I'm trying to develop an application, where user can load a proxy list and software should navigate to a page with different IP's. I'm using some registry settings to do that. But the problem is that when i try to change the IP address it remains the same, in "Internet Options" the IP has changed but on the web page which i'm navigating to, is still shown the IP I navigated first time with. Any solutions about that? or maybe, You have a better solution about this situation. Please suggest something. I tried "httprequest" some moments ago, but that was maddeningly unhelpful. Here's my code:
private void BrowseProxy_Click(object sender, EventArgs e)
{
openFileDialog1.ShowDialog();
ProxySrcTBX.Text = openFileDialog1.FileName;
using (StreamReader r = new StreamReader(ProxySrcTBX.Text))
{
string line;
while ((line = r.ReadLine()) != null)
{
ProxyCount++;
}
r.Close();
}
try { Proxy = File.ReadAllLines(ProxySrcTBX.Text); }
catch (Exception Exception) { MessageBox.Show(Exception.ToString()); }
}
private void SetProxy(string Proxy)
{
string key = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
RegistryKey RegKey = Registry.CurrentUser.OpenSubKey(key, true);
RegKey.SetValue("ProxyServer", Proxy);
RegKey.SetValue("ProxyEnable", 1);
RegKey.Close();
}
private void button6_Click(object sender, EventArgs e)
{
webBrowser1.ScriptErrorsSuppressed = true;
SetProxy(Proxy[ProxyIndex]);
label2.Text = Proxy[ProxyIndex];
currentUri = new Uri("https://myip.ru");
NavigateTo(currentUri);
}
public void NavigateTo(Uri url)
{
url = currentUri;
webBrowser1.Navigate(url);
}
private void Dispose_Click(object sender, EventArgs e)
{
SetProxy("");
string key = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
RegistryKey RegKey = Registry.CurrentUser.OpenSubKey(key, true);
RegKey.SetValue("ProxyEnable", 0);
RegKey.Close();
ProxyIndex++;
}
So, if I understand correctly, you want to have a web browser control, programmatically change its the IP address from which it navigates and it makes it requests through?
This can be done via Interop with WinInet (one of the components that IE and the web browser control use under the covers)
Check out this MSDN blog article
I will go ahead and post the code here so this post is self-contained.
using System;
using System.Runtime.InteropServices;
namespace SetProxy
{
public static class WinInetInterop
{
public static string applicationName;
[DllImport(“wininet.dll”, SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr InternetOpen(
string lpszAgent, int dwAccessType, string lpszProxyName,
string lpszProxyBypass, int dwFlags);
[DllImport(“wininet.dll”, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool InternetCloseHandle(IntPtr hInternet);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct INTERNET_PER_CONN_OPTION_LIST
{
public int Size;
// The connection to be set. NULL means LAN.
public System.IntPtr Connection;
public int OptionCount;
public int OptionError;
// List of INTERNET_PER_CONN_OPTIONs.
public System.IntPtr pOptions;
}
private enum INTERNET_OPTION
{
// Sets or retrieves an INTERNET_PER_CONN_OPTION_LIST structure that specifies
// a list of options for a particular connection.
INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
// Notify the system that the registry settings have been changed so that
// it verifies the settings on the next call to InternetConnect.
INTERNET_OPTION_SETTINGS_CHANGED = 39,
// Causes the proxy data to be reread from the registry for a handle.
INTERNET_OPTION_REFRESH = 37
}
private enum INTERNET_PER_CONN_OptionEnum
{
INTERNET_PER_CONN_FLAGS = 1,
INTERNET_PER_CONN_PROXY_SERVER = 2,
INTERNET_PER_CONN_PROXY_BYPASS = 3,
INTERNET_PER_CONN_AUTOCONFIG_URL = 4,
INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5,
INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL = 6,
INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS = 7,
INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME = 8,
INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL = 9,
INTERNET_PER_CONN_FLAGS_UI = 10
}
private const int INTERNET_OPEN_TYPE_DIRECT = 1; // direct to net
private const int INTERNET_OPEN_TYPE_PRECONFIG = 0; // read registry
/// <summary>
/// Constants used in INTERNET_PER_CONN_OPTON struct.
/// </summary>
private enum INTERNET_OPTION_PER_CONN_FLAGS
{
PROXY_TYPE_DIRECT = 0x00000001, // direct to net
PROXY_TYPE_PROXY = 0x00000002, // via named proxy
PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL
PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection
}
/// <summary>
/// Used in INTERNET_PER_CONN_OPTION.
/// When create a instance of OptionUnion, only one filed will be used.
/// The StructLayout and FieldOffset attributes could help to decrease the struct size.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
private struct INTERNET_PER_CONN_OPTION_OptionUnion
{
// A value in INTERNET_OPTION_PER_CONN_FLAGS.
[FieldOffset(0)]
public int dwValue;
[FieldOffset(0)]
public System.IntPtr pszValue;
[FieldOffset(0)]
public System.Runtime.InteropServices.ComTypes.FILETIME ftValue;
}
[StructLayout(LayoutKind.Sequential)]
private struct INTERNET_PER_CONN_OPTION
{
// A value in INTERNET_PER_CONN_OptionEnum.
public int dwOption;
public INTERNET_PER_CONN_OPTION_OptionUnion Value;
}
/// <summary>
/// Sets an Internet option.
/// </summary>
[DllImport(“wininet.dll”, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern bool InternetSetOption(
IntPtr hInternet,
INTERNET_OPTION dwOption,
IntPtr lpBuffer,
int lpdwBufferLength);
/// <summary>
/// Queries an Internet option on the specified handle. The Handle will be always 0.
/// </summary>
[DllImport(“wininet.dll”, CharSet = CharSet.Ansi, SetLastError = true,
EntryPoint = “InternetQueryOption”)]
private extern static bool InternetQueryOptionList(
IntPtr Handle,
INTERNET_OPTION OptionFlag,
ref INTERNET_PER_CONN_OPTION_LIST OptionList,
ref int size);
/// <summary>
/// Set the proxy server for LAN connection.
/// </summary>
public static bool SetConnectionProxy(string proxyServer )
{
IntPtr hInternet = InternetOpen(applicationName,INTERNET_OPEN_TYPE_DIRECT, null, null, 0);
//// Create 3 options.
//INTERNET_PER_CONN_OPTION[] Options = new INTERNET_PER_CONN_OPTION[3];
// Create 2 options.
INTERNET_PER_CONN_OPTION[] Options = new INTERNET_PER_CONN_OPTION[2];
// Set PROXY flags.
Options[0] = new INTERNET_PER_CONN_OPTION();
Options[0].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_FLAGS;
Options[0].Value.dwValue = (int)INTERNET_OPTION_PER_CONN_FLAGS.PROXY_TYPE_PROXY;
// Set proxy name.
Options[1] = new INTERNET_PER_CONN_OPTION();
Options[1].dwOption =
(int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_SERVER;
Options[1].Value.pszValue = Marshal.StringToHGlobalAnsi(proxyServer);
//// Set proxy bypass.
//Options[2] = new INTERNET_PER_CONN_OPTION();
//Options[2].dwOption =
// (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_BYPASS;
//Options[2].Value.pszValue = Marshal.StringToHGlobalAnsi(“local”);
//// Allocate a block of memory of the options.
//System.IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(Options[0])
// + Marshal.SizeOf(Options[1]) + Marshal.SizeOf(Options[2]));
// Allocate a block of memory of the options.
System.IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(Options[0])
+ Marshal.SizeOf(Options[1]));
System.IntPtr current = buffer;
// Marshal data from a managed object to an unmanaged block of memory.
for (int i = 0; i < Options.Length; i++)
{
Marshal.StructureToPtr(Options[i], current, false);
current = (System.IntPtr)((int)current + Marshal.SizeOf(Options[i]));
}
// Initialize a INTERNET_PER_CONN_OPTION_LIST instance.
INTERNET_PER_CONN_OPTION_LIST option_list = new INTERNET_PER_CONN_OPTION_LIST();
// Point to the allocated memory.
option_list.pOptions = buffer;
// Return the unmanaged size of an object in bytes.
option_list.Size = Marshal.SizeOf(option_list);
// IntPtr.Zero means LAN connection.
option_list.Connection = IntPtr.Zero;
option_list.OptionCount = Options.Length;
option_list.OptionError = 0;
int size = Marshal.SizeOf(option_list);
// Allocate memory for the INTERNET_PER_CONN_OPTION_LIST instance.
IntPtr intptrStruct = Marshal.AllocCoTaskMem(size);
// Marshal data from a managed object to an unmanaged block of memory.
Marshal.StructureToPtr(option_list, intptrStruct, true);
// Set internet settings.
bool bReturn = InternetSetOption(hInternet,
INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION, intptrStruct, size);
// Free the allocated memory.
Marshal.FreeCoTaskMem(buffer);
Marshal.FreeCoTaskMem(intptrStruct);
InternetCloseHandle(hInternet);
// Throw an exception if this operation failed.
if (!bReturn)
{
throw new ApplicationException(” Set Internet Option Failed!”);
}
return bReturn;
}
/// <summary>
/// Backup the current options for LAN connection.
/// Make sure free the memory after restoration.
/// </summary>
private static INTERNET_PER_CONN_OPTION_LIST GetSystemProxy()
{
// Query following options.
INTERNET_PER_CONN_OPTION[] Options = new INTERNET_PER_CONN_OPTION[3];
Options[0] = new INTERNET_PER_CONN_OPTION();
Options[0].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_FLAGS;
Options[1] = new INTERNET_PER_CONN_OPTION();
Options[1].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_SERVER;
Options[2] = new INTERNET_PER_CONN_OPTION();
Options[2].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_BYPASS;
// Allocate a block of memory of the options.
System.IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(Options[0])
+ Marshal.SizeOf(Options[1]) + Marshal.SizeOf(Options[2]));
System.IntPtr current = (System.IntPtr)buffer;
// Marshal data from a managed object to an unmanaged block of memory.
for (int i = 0; i < Options.Length; i++)
{
Marshal.StructureToPtr(Options[i], current, false);
current = (System.IntPtr)((int)current + Marshal.SizeOf(Options[i]));
}
// Initialize a INTERNET_PER_CONN_OPTION_LIST instance.
INTERNET_PER_CONN_OPTION_LIST Request = new INTERNET_PER_CONN_OPTION_LIST();
// Point to the allocated memory.
Request.pOptions = buffer;
Request.Size = Marshal.SizeOf(Request);
// IntPtr.Zero means LAN connection.
Request.Connection = IntPtr.Zero;
Request.OptionCount = Options.Length;
Request.OptionError = 0;
int size = Marshal.SizeOf(Request);
// Query internet options.
bool result = InternetQueryOptionList(IntPtr.Zero,
INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
ref Request, ref size);
if (!result)
{
throw new ApplicationException(” Set Internet Option Failed! “);
}
return Request;
}
/// <summary>
/// Restore the options for LAN connection.
/// </summary>
/// <param name=”request”></param>
/// <returns></returns>
public static bool RestoreSystemProxy()
{
IntPtr hInternet = InternetOpen(applicationName, INTERNET_OPEN_TYPE_DIRECT, null, null, 0);
INTERNET_PER_CONN_OPTION_LIST request = GetSystemProxy();
int size = Marshal.SizeOf(request);
// Allocate memory.
IntPtr intptrStruct = Marshal.AllocCoTaskMem(size);
// Convert structure to IntPtr
Marshal.StructureToPtr(request, intptrStruct, true);
// Set internet options.
bool bReturn = InternetSetOption(hInternet,
INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
intptrStruct, size);
// Free the allocated memory.
Marshal.FreeCoTaskMem(request.pOptions);
Marshal.FreeCoTaskMem(intptrStruct);
if (!bReturn)
{
throw new ApplicationException(” Set Internet Option Failed! “);
}
// Notify the system that the registry settings have been changed and cause
// the proxy data to be reread from the registry for a handle.
InternetSetOption(hInternet, INTERNET_OPTION.INTERNET_OPTION_SETTINGS_CHANGED,
IntPtr.Zero, 0);
InternetSetOption(hInternet, INTERNET_OPTION.INTERNET_OPTION_REFRESH,
IntPtr.Zero, 0);
InternetCloseHandle(hInternet);
return bReturn;
}
}
}
Related
I'm trying to attach a console to a Service if im running it from the debugger.
I've read up a couple of "working" solutions, but those seem not to really work. Here is he Code I use:
public static void RunService(Func<ServiceBase> factory)
{
if (Debugger.IsAttached)
{
Utils.AttachConsole();
Console.Write($"Starting service ");
var instance = factory();
Console.WriteLine(instance.GetType().Name);
//Invoke start Method
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
//Stop service
}
else
{
ServiceBase.Run(factory());
}
}
Alloc Console is:
public static void AttachConsole()
{
var ret = NativeMethods.AllocConsole();
IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);
NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));
TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
Console.SetOut(writer);
}
and the Interop includes:
internal static class NativeMethods
{
internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5;
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll")]
internal static extern IntPtr GetStdHandle(uint nStdHandle);
[DllImport("kernel32.dll")]
internal static extern void SetStdHandle(uint nStdHandle, IntPtr handle);
}
What happens is, a console is created and attached, but there is no output. Can't be to difficult, but I'm to dumb to see it :(
EDIT:
The Issue is Visual Studio, it is not the code "itself". Without VS, I can get a console and receive the expected output there. There is some kind of redirection in VS, which I am looking to overcome here.
EDIT just for Hans - here is the "full code"
static void Main(string[] args)
{
ServiceLauncher.RunService(() => new FactoryService();
}
The project is set to application type windows.
I was able to reproduce your issue and get it working on my machine. Some of your code looks like it comes from the accepted answer at No console output when using AllocConsole and target architecture x86. If you read the comment thread under that answer, you will see that new IntPtr(7) does not work as of Windows 7/ Server 2012. The "new magic number" for Windows 7 also did not work for me. To solve this, I started down the path of porting the given c++ call from the comments into c#, which required some signature changes for the PInvokes (which were all copied and pasted from PInvoke.net, so they should be fine). The changes that I made are almost exclusively in the PInvoke code. Here is a full working code set:
Program.cs (unchanged):
static void Main()
{
ServiceLauncher.RunService(() => new Service1());
}
ServiceLauncher.cs (unchanged):
public static void RunService(Func<ServiceBase> factory)
{
if (Debugger.IsAttached)
{
Utils.AttachConsole();
Console.Write($"Starting service ");
var instance = factory();
Console.WriteLine(instance.GetType().Name);
//Invoke start Method
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
//Stop service
}
else
{
ServiceBase.Run(factory());
}
}
Utils.cs (1 change as documemented in comments):
public static void AttachConsole()
{
var ret = NativeMethods.AllocConsole();
IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);
// IntPtr(7) was a dangerous assumption that doesn't work on current versions of Windows...
//NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));
// Instead, get the defaultStdOut using PInvoke
SafeFileHandle defaultStdOut = NativeMethods.CreateFile("CONOUT$", EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.Write, IntPtr.Zero, ECreationDisposition.OpenExisting, 0, IntPtr.Zero);
NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, defaultStdOut.DangerousGetHandle()); // also seems dangerous... there may be an alternate signature for SetStdHandle that takes SafeFileHandle.
TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
Console.SetOut(writer);
}
NativeMethods.cs (almost completely different - links and explanations given in comments). Enums included in this file (outside of the class scope) but can be moved to different files at your discretion:
internal static class NativeMethods
{
// 0xFFFFFFF5 is not consistent with what I found...
//internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5;
// https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
internal const int STD_OUTPUT_HANDLE = -11;
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
// method signature changed per https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int nStdHandle);
// method signature changed per https://www.pinvoke.net/default.aspx/kernel32.setstdhandle
[DllImport("kernel32.dll")]
internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFileHandle CreateFile(
string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
}
// ENUMS FROM http://www.pinvoke.net/default.aspx/kernel32/CreateFile.html
[Flags]
public enum EFileAccess : uint
{
//
// Standart Section
//
AccessSystemSecurity = 0x1000000, // AccessSystemAcl access type
MaximumAllowed = 0x2000000, // MaximumAllowed access type
Delete = 0x10000,
ReadControl = 0x20000,
WriteDAC = 0x40000,
WriteOwner = 0x80000,
Synchronize = 0x100000,
StandardRightsRequired = 0xF0000,
StandardRightsRead = ReadControl,
StandardRightsWrite = ReadControl,
StandardRightsExecute = ReadControl,
StandardRightsAll = 0x1F0000,
SpecificRightsAll = 0xFFFF,
FILE_READ_DATA = 0x0001, // file & pipe
FILE_LIST_DIRECTORY = 0x0001, // directory
FILE_WRITE_DATA = 0x0002, // file & pipe
FILE_ADD_FILE = 0x0002, // directory
FILE_APPEND_DATA = 0x0004, // file
FILE_ADD_SUBDIRECTORY = 0x0004, // directory
FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
FILE_READ_EA = 0x0008, // file & directory
FILE_WRITE_EA = 0x0010, // file & directory
FILE_EXECUTE = 0x0020, // file
FILE_TRAVERSE = 0x0020, // directory
FILE_DELETE_CHILD = 0x0040, // directory
FILE_READ_ATTRIBUTES = 0x0080, // all
FILE_WRITE_ATTRIBUTES = 0x0100, // all
//
// Generic Section
//
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000,
SPECIFIC_RIGHTS_ALL = 0x00FFFF,
FILE_ALL_ACCESS =
StandardRightsRequired |
Synchronize |
0x1FF,
FILE_GENERIC_READ =
StandardRightsRead |
FILE_READ_DATA |
FILE_READ_ATTRIBUTES |
FILE_READ_EA |
Synchronize,
FILE_GENERIC_WRITE =
StandardRightsWrite |
FILE_WRITE_DATA |
FILE_WRITE_ATTRIBUTES |
FILE_WRITE_EA |
FILE_APPEND_DATA |
Synchronize,
FILE_GENERIC_EXECUTE =
StandardRightsExecute |
FILE_READ_ATTRIBUTES |
FILE_EXECUTE |
Synchronize
}
[Flags]
public enum EFileShare : uint
{
/// <summary>
///
/// </summary>
None = 0x00000000,
/// <summary>
/// Enables subsequent open operations on an object to request read access.
/// Otherwise, other processes cannot open the object if they request read access.
/// If this flag is not specified, but the object has been opened for read access, the function fails.
/// </summary>
Read = 0x00000001,
/// <summary>
/// Enables subsequent open operations on an object to request write access.
/// Otherwise, other processes cannot open the object if they request write access.
/// If this flag is not specified, but the object has been opened for write access, the function fails.
/// </summary>
Write = 0x00000002,
/// <summary>
/// Enables subsequent open operations on an object to request delete access.
/// Otherwise, other processes cannot open the object if they request delete access.
/// If this flag is not specified, but the object has been opened for delete access, the function fails.
/// </summary>
Delete = 0x00000004
}
public enum ECreationDisposition : uint
{
/// <summary>
/// Creates a new file. The function fails if a specified file exists.
/// </summary>
New = 1,
/// <summary>
/// Creates a new file, always.
/// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes,
/// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
/// </summary>
CreateAlways = 2,
/// <summary>
/// Opens a file. The function fails if the file does not exist.
/// </summary>
OpenExisting = 3,
/// <summary>
/// Opens a file, always.
/// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
/// </summary>
OpenAlways = 4,
/// <summary>
/// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
/// The calling process must open the file with the GENERIC_WRITE access right.
/// </summary>
TruncateExisting = 5
}
[Flags]
public enum EFileAttributes : uint
{
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
I do this for every windows service that I develop. But usually I use a winform and even in some cases named pipes for being able to do more fancy stuff than watching the output in console.
That being said you don't have to do anything fancy to get console output for windows services.
1) Create a windows service project
2) change your Project output to Console application.
3) change the "Service1" class to this.
using System.ServiceProcess;
namespace WindowsService1
{
public partial class Service1 : ServiceBase
{
readonly Runner _runner = new Runner();
static void Main(string[] args)
{
var service = new Service1();
if (Debugger.IsAttached)
{
service.OnStart(args);
Console.WriteLine("Find the any key!");
Console.Read();
service.OnStop();
}
else
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
service
};
ServiceBase.Run(ServicesToRun);
}
}
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
_runner.Run();
}
protected override void OnStop()
{
_runner.Stop();
}
}
}
Then in the Runner class you do what ever you are getting from your factory method.
I've always found it easier to use Topshelf to debug service applications (http://topshelf-project.com/)
Computer A is on Domain A
Computer B is on Domain B
Domain A and Domain B have a trust allowing Accounts to connect to the other domain's computers
Account A is Admin on Computer A and has Backup&Restore Privileges
Account B is Admin on Computer B and has Backup&Restore Privileges
Account A IS NOT Admin on Computer B and does not have Backup&Restore Privileges
Account B IS NOT Admin on Computer A and does not have Backup&Restore Privileges
A person owns both Account A and Account B.
They want to copy a file from Computer A to Computer B.
They wish to copy this file from and into a folder they do not have permissions for. Therefore, they must both enable Backup&Restore Privileges on their Access Token they got from LogonUser
It is not allowed to change the DACL to grant these permissions on any folders or files and instead both Account A and Account B must enable B&R privileges which is recognized by the Computer, only then will they be able to copy the file without Access Denied.
The problem is I have tried using NETWORK, INTERACTIVE, and NEW_CREDENTIALS Access Tokens and enabling privileges on them, but only NETWORK contains the privileges and they are already enabled by default. Then when I try to WindowsIdentity.Impersonate the NETWORK token and then call "CreateFile" to open the file with privileges, it fails and returns an invalid file handle of -1. I can use INTERACTIVE to read an unrestricted file but it didn't have Access Token Privileges needed ( SeBackupPrivilege & SeRestorePrivilege ) when it was returned from LogonUser. I assume once Impersonate happens it generates a Token that "might" have those privileges, but I assume that's based on the machine the code is running on.
Is there a way to Impersonate Access Token -> Enable Access Token B&R Privileges on the remote computers they would have normally when at that machine running as administrator which could be enabled.
OR
Is there a way to use the NETWORK Token with Impersonation to successfully copy the file from Computer on Domain A to Computer on Domain B. If I run the program as Account B who isn't admin trying to impersonate Account A with network credential, it appears to not work on impersonation
Below is demo code in a Console Application that emulates the situation. You must change parts of it to test it accordingly:
You must create the file paths and the file to be read.
You must edit permissions on "DENYTHEIMPERSONATINGUSERHERE" folder so the impersonating user account is denied and must use privileges.
You must enter an actual account credentials to get an AccessToken.
class Program
{
static void Main(string[] args)
{
//Credentials of Account A
string usernameA = "AccountA";
string DomainA = "DomainA.com";
SecureString passwordA = new SecureString();
passwordA.AppendChar('P');
passwordA.AppendChar('W');
passwordA.AppendChar('D');
passwordA.AppendChar('A');
IntPtr AccessToken = IntPtr.Zero;
//Getting Network Access Token. (Network is an Impersonation Token, most other types are returned as Primary Tokens)
AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_NETWORK, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);
/*
//Getting INTERACTIVE Access Token. (returns Primary Token)
//Impersonation will work but won't have enabled privileges so will error when trying to write file.
AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_INTERACTIVE, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);
*/
if(AccessToken == IntPtr.Zero || AccessToken.ToInt32() == -1)
{
//Not valid creds
System.Diagnostics.Debug.WriteLine(lgnCode.ToString());
return;
}
//Enable Token Security Privileges
AccessTokenHelper.ElevateSecurityPrivileges(AccessToken);
//List Enabled Token Privileges
List<string> tokenEnabledPrivileges = AccessTokenHelper.ListEnabledPrivileges(AccessToken);
foreach(string s in tokenEnabledPrivileges)
{
System.Diagnostics.Debug.WriteLine(s);
}
string sourceFile = #"C:\Temp\Test1\TestData.txt";
string destFile = #"C:\Temp\Test1\DENYTHEIMPERSONATINGUSERHERE\PrivCopy.txt";
bool bCopied = PrivilegedEnterpriseCopyFile(sourceFile, destFile, AccessToken, AccessToken);
System.Diagnostics.Debug.WriteLine("DID THE COPY WORK? " + bCopied.ToString().ToUpper());
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
OPENFILE_SHAREACCESS dwShareMode, //If you are reading folders you better have OPENFILE_SHAREACCESS.FILE_SHARE_READ
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
private enum OPENFILE_SHAREACCESS : uint
{
NO_SHARING = 0x00000000,
FILE_SHARE_READ = 0x00000001,
FILE_SHARE_WRITE = 0x00000002,
FILE_SHARE_DELETE = 0x00000004
};
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetFileSizeEx(IntPtr hFile, out long lpFileSize);
/// <summary>
/// Goal: Enterprise Level Copy.... can copy file data between remote machines between domains.
/// </summary>
public static bool PrivilegedEnterpriseCopyFile(string sourceAbsoluteFileName, string destAbsoluteFileName, IntPtr sourceAccessToken, IntPtr destAccessToken, bool bOverwrite = false)
{
const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000; //If you are writing to a file, best allow both READ and WRITE because Windows Performance suffers without.
const uint CREATE_ALWAYS = 2; //Create and overwrite if necesarry
const uint OPEN_EXISTING = 3; //Open only if exists
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; //This makes it take enabled Backup&Restore privileges into account
const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; //This helps speed up reading.
const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; //This helps speed up writing!
//We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K).
// The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant
// improvement in Copy performance.
const int DefaultCopyBufferSize = 81920; //80kb
bool bSuccess = false;
//Get File Ptr Handles. DO NOT ALLOW DELETE ON SOURCE OR DEST WHILE THIS IS HAPPENING.
//Get the file pointer by using an access token with access.
IntPtr pSource = IntPtr.Zero;
IntPtr pDest = IntPtr.Zero;
//As source user, connect for optimized READING
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(sourceAccessToken))
{
pSource = CreateFile(sourceAbsoluteFileName, GENERIC_READ, OPENFILE_SHAREACCESS.FILE_SHARE_READ,
IntPtr.Zero, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero);
}
if (pSource == IntPtr.Zero || pSource.ToInt32() == -1)
{
//Failed to get Source, return false
return bSuccess;
}
//Get Dest Path
string DestPath = Path.GetDirectoryName(destAbsoluteFileName);
string DestFileName = Path.GetFileName(destAbsoluteFileName);
//As dest user
using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(destAccessToken))
{
try
{
bool bProceed = true;
if (!bOverwrite)
{
//We don't want to overwrite existing file, ensure it doesn't exist.
List<string> files = Directory.EnumerateFiles(DestPath).ToList();
if (files.Any(s => s.Equals(DestFileName, StringComparison.OrdinalIgnoreCase)))
{
//File exists, do not proceed
bProceed = false;
}
}
//Do we proceed?
if (bProceed)
{
//Create/Overwrite existing File
pDest = CreateFile(destAbsoluteFileName, GENERIC_READ | GENERIC_WRITE, OPENFILE_SHAREACCESS.NO_SHARING,
IntPtr.Zero, CREATE_ALWAYS,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_WRITE_THROUGH, IntPtr.Zero);
}
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
//If we successfully have both File Handles, we can proceed with Reading from source and writing to Dest.
//If valid file pointers!!!
if (pSource != IntPtr.Zero && pSource.ToInt32() != -1 &&
pDest != IntPtr.Zero && pDest.ToInt32() != -1)
{
//Put Handle into more supported SafeFileHandle Ptr.
SafeFileHandle safeSourcePtr = new SafeFileHandle(pSource, false); //We will close handle manually
SafeFileHandle safeDestPtr = new SafeFileHandle(pDest, false); //We will close handle manually
try
{
using (FileStream fsSource = new FileStream(safeSourcePtr, FileAccess.Read, DefaultCopyBufferSize))
{
using (FileStream fsDest = new FileStream(safeDestPtr, FileAccess.ReadWrite, DefaultCopyBufferSize))
{
//Here we read X bytes up to limit from fsSource and write to fsDest, until no more bytes!
// Read the source file into a byte array.
byte[] buffer = new byte[DefaultCopyBufferSize];
int bytesRead = -1;
while ((bytesRead = fsSource.Read(buffer, 0, buffer.Length)) > 0)
{
fsDest.Write(buffer, 0, bytesRead);
}
//Force data to be flushed to harddisk.
fsDest.Flush(true);
}
}
}
catch { }
//Compare File Size to know if we successfully wrote bytes to destination!
//We'll assume it'd error out if it didn't.
GetFileSizeEx(pSource, out long sourceSize);
GetFileSizeEx(pDest, out long destSize);
if (sourceSize == destSize)
{
//consider it a success
bSuccess = true;
}
}
if (pSource != IntPtr.Zero && pSource.ToInt32() != -1)
{
//Close file handle manually
CloseHandle(pSource);
}
if (pDest != IntPtr.Zero && pDest.ToInt32() != -1)
{
//Close file handle manually
CloseHandle(pDest);
}
return bSuccess;
}
}
public class AccessTokenHelper
{
public enum LOGON32_LOGONTYPE : int
{
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,
LOGON32_LOGON_NEW_CREDENTIALS = 9,
};
public enum LOGON32_LOGONPROVIDER : int
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3,
};
public enum LOGON32_LOGONERROR : int
{
ERROR_SUCCESS = 0,
ERROR_NO_LOGON_SERVERS = 1311,
ERROR_INVALID_ACCOUNT_NAME = 1315,
ERROR_LOGON_FAILURE = 1326,
ERROR_ACCOUNT_RESTRICTION = 1327,
ERROR_INVALID_LOGON_HOURS = 1328,
ERROR_INVALID_WORKSTATION_LOGONDENIED = 1329,
ERROR_PASSWORD_EXPIRED = 1330,
ERROR_ACCOUNT_DISABLED = 1331,
ERROR_INVALID_LOGON_TYPE = 1367,
ERROR_LOGON_NOT_GRANTED = 1380,
ERROR_NETLOGON_NOT_STARTED = 1792,
ERROR_ACCOUNT_EXPIRED = 1793,
ERROR_PASSWORD_MUST_CHANGE = 1907,
ERROR_ACCOUNT_LOCKED_OUT = 1909,
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private extern static bool LogonUser(string username, string domain, IntPtr password, LOGON32_LOGONTYPE logonType, LOGON32_LOGONPROVIDER logonProvider, out IntPtr token);
/// <summary>
/// Attempts to create Access token from information given.
/// </summary>
public static LOGON32_LOGONERROR GetAccessToken(string domain, string username, SecureString securepassword, LOGON32_LOGONTYPE eLOGONTYPE, LOGON32_LOGONPROVIDER eLOGONPROVIDER, out IntPtr token)
{
token = IntPtr.Zero;
// Marshal the SecureString to unmanaged memory. I hate doing this but it's currently most secure way offered by Microsoft to get Access Token from Credentials.
IntPtr passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(securepassword);
bool bSuccess = LogonUser(username,
domain,
passwordPtr,
eLOGONTYPE,
eLOGONPROVIDER,
out token);
//return the error code, useful if not successful.
int errResult = Marshal.GetLastWin32Error();
// Zero-out and free the unmanaged string reference.
Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
return (LOGON32_LOGONERROR)errResult;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeName(string lpSystemName, IntPtr lpLuid, StringBuilder lpName, ref int cchName);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeDisplayName(string systemName, string privilegeName, StringBuilder displayName, ref int cchDisplayName, out uint languageId);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint prevStateBuffer,
IntPtr prevStateNA,
IntPtr prevBufferNA);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetTokenInformation(
IntPtr hToken,
uint TokenInformationClass,
IntPtr TokenInformation,
int TokenInformationLength,
out int ReturnLength);
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public uint HighPart;
};
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public int PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
};
[Flags]
public enum SE_PRIVILEGE_STATE : uint
{
SE_PRIVILEGE_DISABLED = 0x00,
SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x01,
SE_PRIVILEGE_ENABLED = 0x02,
SE_PRIVILEGE_REMOVED = 0x04,
SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
};
/// <summary>
/// Will elevate Backup and Restore Privileges of Access Token (be it Primary Type or Impersonation Type) if Access Token has Privilege
/// 1) SeBackupPrivilege -- this grants read access regardless of DACL entries.
/// 2) SeRestorePrivilege -- this grants write access regardless of DACL entries.
/// 3) SeSecurityPrivilege -- this grants access to SACL for audits/security log.
/// </summary>
public static void ElevateSecurityPrivileges(IntPtr hToken, bool bEnabled = true)
{
SE_PRIVILEGE_STATE privSTATE = bEnabled ? SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_STATE.SE_PRIVILEGE_DISABLED;
List<string> SecurityPrivNames = new List<string>() { "SeBackupPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege" };
if (hToken != IntPtr.Zero)
{
AdjustAccessTokenPrivileges(hToken, SecurityPrivNames, privSTATE);
}
}
public static void AdjustAccessTokenPrivileges(IntPtr TokenHandle, List<string> PrivilegeNames, SE_PRIVILEGE_STATE eNewPrivilegeState, string remoteMachineName = null)
{
if (TokenHandle != IntPtr.Zero && PrivilegeNames != null && PrivilegeNames.Count > 0)
{
DataTable privDT = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);
if (privDT != null && privDT.Rows.Count > 0)
{
//If we have privileges, try to set state!
foreach (string privName in PrivilegeNames)
{
DataRow row = privDT.Select(string.Format("[{0}]='{1}'", privDT.Columns[1].ColumnName, privName)).FirstOrDefault();
if (row != null)
{
UpdateExistingTokenPrivilege(TokenHandle, row.Field<LUID>(0), eNewPrivilegeState);
}
}
}
}
}
private static bool UpdateExistingTokenPrivilege(IntPtr tokenHandle, LUID privLuid, SE_PRIVILEGE_STATE eNewPrivilegeState)
{
//Create our updated tokenPriv to send what privs we want updated.
TOKEN_PRIVILEGES tokenPrivs = new TOKEN_PRIVILEGES();
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges = new LUID_AND_ATTRIBUTES[] { new LUID_AND_ATTRIBUTES() { Luid = privLuid, Attributes = (uint)eNewPrivilegeState } };
//Adjust Token Privilege!
bool bSuccess = AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivs, 0, IntPtr.Zero, IntPtr.Zero);
//Return result of trying to adjust token privilege.
return bSuccess;
}
public static DataTable GetAccessTokenPrivilegesAsDataTable(IntPtr TokenHandle, string remoteMachineName = null)
{
TOKEN_PRIVILEGES tokenPrivData = GetAccessTokenPrivileges(TokenHandle);
DataTable privDT = new DataTable();
privDT.Columns.Add("PrivilegeLUID", typeof(LUID));
privDT.Columns.Add("PrivilegeName");
privDT.Columns.Add("PrivilegeState", typeof(SE_PRIVILEGE_STATE));
foreach (LUID_AND_ATTRIBUTES privData in tokenPrivData.Privileges)
{
string PrivilegeName = LookupPrivilegeName(privData.Luid, remoteMachineName);
if (!string.IsNullOrEmpty(PrivilegeName))
{
DataRow row = privDT.NewRow();
row[0] = privData.Luid;
row[1] = PrivilegeName;
row[2] = (SE_PRIVILEGE_STATE)privData.Attributes;
//Add Row
privDT.Rows.Add(row);
}
}
return privDT;
}
private static TOKEN_PRIVILEGES GetAccessTokenPrivileges(IntPtr TokenHandle)
{
uint TOKEN_INFORMATION_CLASS_TokenPrivileges = 3;
if (TokenHandle != IntPtr.Zero)
{
int nBufferSize = 0;
bool TokenInfoResult;
//First call is to get buffer size!
TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, IntPtr.Zero, nBufferSize, out nBufferSize);
//Allocate Token Info correctly.
IntPtr pTokenInfo = Marshal.AllocHGlobal(nBufferSize);
//Get our Token Info Data
TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, pTokenInfo, nBufferSize, out nBufferSize);
if (TokenInfoResult)
{
TOKEN_PRIVILEGES returnedPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(pTokenInfo, typeof(TOKEN_PRIVILEGES));
int PrivilegeCount = returnedPrivilegeSet.PrivilegeCount;
//lets create the array we should have had returned in the first place
LUID_AND_ATTRIBUTES[] AllPrivs = new LUID_AND_ATTRIBUTES[PrivilegeCount]; //initialize an array to the right size, including 0!
if (PrivilegeCount > 0)
{
LUID_AND_ATTRIBUTES currentPriv = new LUID_AND_ATTRIBUTES();
//pPrivileges will hold our new location to read from by taking the last pointer plus the size of the last structure read
IntPtr pPrivilege = new IntPtr(pTokenInfo.ToInt32() + sizeof(int)); //pointer math, we point to the first element of Privileges array in the struct.
currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
AllPrivs[0] = currentPriv; //We'll add the first element to our array.
//After getting our first structure we can loop through the rest since they will all be the same
for (int i = 1; i < PrivilegeCount; ++i)
{
pPrivilege = new IntPtr(pPrivilege.ToInt32() + Marshal.SizeOf(currentPriv)); //This will point to the next Privilege element in the array
currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
AllPrivs[i] = currentPriv; //Add element to the array
}
}
//Create our complete struct of TOKEN_PRIVILEGES
TOKEN_PRIVILEGES completePrivilegeSet = new TOKEN_PRIVILEGES();
completePrivilegeSet.PrivilegeCount = PrivilegeCount;
completePrivilegeSet.Privileges = AllPrivs;
//We can get release all the pointers now, we got what we wanted!
Marshal.FreeHGlobal(pTokenInfo); //Free up the reserved space in unmanaged memory (Should be done any time AllocHGlobal is used)
//Return our completePrivilegeSet!
return completePrivilegeSet;
}
}
return new TOKEN_PRIVILEGES() { PrivilegeCount = 0, Privileges = new LUID_AND_ATTRIBUTES[] { } };
}
private static string LookupPrivilegeName(LUID privLuid, string remoteMachineName = null)
{
string PrivilegeName = null;
StringBuilder sb = new StringBuilder();
int cchName = 0; //Holds the length of structure we will be receiving LookupPrivilagename
IntPtr ipLuid = Marshal.AllocHGlobal(Marshal.SizeOf(privLuid)); //Allocate a block of memory large enough to hold the structure
Marshal.StructureToPtr(privLuid, ipLuid, true); //Write the structure into the reserved space in unmanaged memory
LookupPrivilegeName(remoteMachineName, ipLuid, null, ref cchName); // call once to get the name length we will be receiving
sb.Capacity = cchName; //Our string builder is buffered for the name!
if (LookupPrivilegeName(remoteMachineName, ipLuid, sb, ref cchName))
{
// Successfully retrieved name!
PrivilegeName = sb.ToString();
}
Marshal.FreeHGlobal(ipLuid); //Free up the reserved space in unmanaged memory
return PrivilegeName;
}
public static List<string> ListEnabledPrivileges(IntPtr TokenHandle, string remoteMachineName = null)
{
List<string> enabledPrivs = null;
DataTable dt = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);
if (dt != null && dt.Rows.Count > 0)
{
uint nEnabled = (uint)SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED;
uint nEnabledAndDefault = (uint)(SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED);
string query = string.Format("[{0}] IN ( {1}, {2} )"
, dt.Columns[2].ColumnName
, nEnabled
, nEnabledAndDefault);
IEnumerable<DataRow> rows = dt.Select(query);
if (rows != null && rows.Count() > 0)
{
enabledPrivs = dt.Select(query).Select(r => r.Field<string>(1)).ToList();
}
}
return enabledPrivs;
}
}
So after much research it turns out this is locked down in Windows. Unless the current process token has SeTcbPrivilege (Act as Part of Operating System) and it's enabled, you cannot get an unrestricted/elevated token when deciding to impersonate from a non-elevated token.
Also if you then try to get the LinkedToken/Unrestricted Token of an Access Token but do not have the SeTcbPrivilege enabled, you will end up with a Token that has a SECURITY_IMPERSONATION_LEVEL of SecurityIdentification which cannot be used for impersonation. Without SECURITY_IMPERSONATION_LEVEL being SecurityImpersonation (Local Access) or SecurityDelegation (Local+Remote Access), you cannot impersonate the Unrestricted Access Token and will result in failure with LastError being ERROR_BAD_IMPERSONATION_LEVEL (Either a required impersonation level was not provided, or the provided impersonation level is invalid).
This means if you do not have SeTcbPrivilege and it is not enable on the token, it is required to have an elevated process token (ex. Run As Admin was used to start the program manually or forcefully via requestedExecutionLevel level="requireAdministrator") then Windows will not strip the privileges on Access Token when you decide to impersonate the Access Token.
By using a local administrator account that runs the program as administrator, and then using domain account credentials that are an admin on another machine to get an access token using the params of LOGON32_LOGONTYPE.LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50. It is then possible to elevate privileges on the NEWCRED token, impersonate the NEWCRED token which will not strip away the privileges, and have it successfully connect to another computer and read from a file and write to a file on another computer to which it has no permissions or is even explicitly denied, proving it was using the token backup and restore privileges!
Helpful Sources:
how do i convert restricted user token to unrestricted one?
How to call LogonUser() to get a non-restricted full token inside a Windows Service with UAC enabled?
How can I find out if another user (by login name) has access to read a file?
Does this require me to have to use DirectoryInfo.GetAccessControl().GetAccessRules(...) to get the Access Control List for a specific file, and then query AD for the target user's entire list of AD group memberships (direct or indirect) and then compare if there is any matching group associated with the target file?
Is there any other way to do this? Some of these AD queries are taking 30 seconds due to the sheer number of group memberships a user may have.
Windows can resolve very quickly if the logged in user has access to files. If anyone can shed some light on how it is doing this so I can make the same method calls to check if another user has access, then please let me know.
The reason I need to do this is because I have links on a web page to return files from a network drive. Some browsers (e.g. chrome) cannot be redirected to UNC paths, so they are redirected to a .NET handler/page which uses impersonation. Due to double-hop, the handler can not access the UNC path to as the .NET impersonated user, so therefore I need to use a service account, but it needs to determine whether a specific user by login is allowed to read a file before returning it.
Any alternatives are welcome.
Windows provides the AccessCheck function, note that the user being checked is represented by a token. As NetMage commented, the group membership is fetched from AD only during token creation and cached, so it doesn't have to be repeated for each security check.
A function with similar capability but a simpler interface is GetEffectiveRightsFromAcl.
I don't know whether there is a managed API for doing this with a .NET SecurityPrincipal. It may be that you have to p/invoke AccessCheck. Trying to duplicate the logic for checking ACLs is definitely not recommended.
For anyone else who needs to do this. This is what i did.
[DllImport("advapi32.dll")]
private static extern uint GetEffectiveRightsFromAcl(byte[] pacl, ref TRUSTEE pTrustee, ref uint pAccessRights);
private enum MULTIPLE_TRUSTEE_OPERATION
{
NO_MULTIPLE_TRUSTEE,
TRUSTEE_IS_IMPERSONATE
}
private enum TRUSTEE_FORM
{
TRUSTEE_IS_SID,
TRUSTEE_IS_NAME,
TRUSTEE_BAD_FORM,
TRUSTEE_IS_OBJECTS_AND_SID,
TRUSTEE_IS_OBJECTS_AND_NAME
}
private enum TRUSTEE_TYPE
{
TRUSTEE_IS_UNKNOWN,
TRUSTEE_IS_USER,
TRUSTEE_IS_GROUP,
TRUSTEE_IS_DOMAIN,
TRUSTEE_IS_ALIAS,
TRUSTEE_IS_WELL_KNOWN_GROUP,
TRUSTEE_IS_DELETED,
TRUSTEE_IS_INVALID,
TRUSTEE_IS_COMPUTER
}
private struct TRUSTEE
{
public IntPtr pMultipleTrustee;
public MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
public TRUSTEE_FORM TrusteeForm;
public TRUSTEE_TYPE TrusteeType;
public IntPtr ptstrName;
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern void BuildTrusteeWithSid(
ref TRUSTEE pTrustee,
byte[] sid
);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool QueryServiceObjectSecurity(SafeHandle serviceHandle, System.Security.AccessControl.SecurityInfos secInfo, byte[] lpSecDesrBuf, uint bufSize, out uint bufSizeNeeded);
// Reference for these flags: https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights(v=vs.110).aspx
[System.FlagsAttribute]
public enum ServiceAccessFlags : uint
{
QueryConfig = 1,
ChangeConfig = 2,
QueryStatus = 4,
EnumerateDependents = 8,
Start = 16,
Stop = 32,
PauseContinue = 64,
Interrogate = 128,
UserDefinedControl = 256,
Delete = 65536,
ReadControl = 131072,
WriteDac = 262144,
WriteOwner = 524288,
Synchronize = 1048576,
AccessSystemSecurity = 16777216,
GenericAll = 268435456,
GenericExecute = 536870912,
GenericWrite = 1073741824,
GenericRead = 2147483648
}
public enum EXTENDED_NAME_FORMAT
{
NameUnknown = 0,
NameFullyQualifiedDN = 1,
NameSamCompatible = 2,
NameDisplay = 3,
NameUniqueId = 6,
NameCanonical = 7,
NameUserPrincipal = 8,
NameCanonicalEx = 9,
NameServicePrincipal = 10,
NameDnsDomain = 12
}
[DllImport("secur32.dll", CharSet = CharSet.Auto)]
public static extern int GetUserNameEx(int nameFormat, StringBuilder userName, ref int userNameSize);
public static string GetUserUpn()
{
string upn = null;
StringBuilder userName = new StringBuilder(1024);
int userNameSize = userName.Capacity;
if (GetUserNameEx((int)EXTENDED_NAME_FORMAT.NameUserPrincipal, userName, ref userNameSize) != 0)
{
upn = userName.ToString();
}
return upn;
}
/// <summary>
/// Returns the user access flags by path and UPN. This can be used to determine the level of access another user has to a file.
/// </summary>
public static ServiceAccessFlags? GetUserPermission(string path, string upn)
{
WindowsIdentity windowsIdentity = new WindowsIdentity(upn);
DirectoryInfo di = new DirectoryInfo(path);
DirectorySecurity ds = di.GetAccessControl();
RawSecurityDescriptor rsd = new RawSecurityDescriptor(ds.GetSecurityDescriptorBinaryForm(), 0);
RawAcl racl = rsd.DiscretionaryAcl;
DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, racl);
byte[] daclBuffer = new byte[dacl.BinaryLength];
dacl.GetBinaryForm(daclBuffer, 0);
SecurityIdentifier sid = windowsIdentity.User;
byte[] sidBuffer = new byte[sid.BinaryLength];
sid.GetBinaryForm(sidBuffer, 0);
TRUSTEE t = new TRUSTEE();
BuildTrusteeWithSid(ref t, sidBuffer);
uint access = 0;
uint hr = GetEffectiveRightsFromAcl(daclBuffer, ref t, ref access);
ServiceAccessFlags serviceAccess = (ServiceAccessFlags)access;
int i = Marshal.Release(t.ptstrName);
return serviceAccess;
}
Usage example:
FileService.ServiceAccessFlags? flags = null;
try { flags = FileService.GetUserPermission(uncFilePath, upn); }
catch (DirectoryNotFoundException ex)
{
LogWarning("Could not resolve permission because the file was not found as the service account (" + FileService.GetUserUpn() + ")");
}
catch (Exception ex)
{
LogWarning("Could not resolve permission on this file (" + FileService.GetUserUpn() + "): " + ex.Message);
}
if (flags.HasValue)
{
// Check for Read access
if ((flags.Value & FileService.ServiceAccessFlags.ReadControl) == FileService.ServiceAccessFlags.ReadControl)
{
hasPermission = true;
LogMessage("User has access (upn: " + upn + "): " + flags.ToString());
}
else
{
hasPermission = false;
LogError("User does not have read access to file (upn: " + upn + "): " + flags.ToString());
}
}
I have a requirement witch is to execute a vbscript located in a shared network drive. ie: \SERVER1\shared$\path\script.vbs
To connect to this shared folder I need to pass credentials ie:
DOMAIN\AdminShare
1234
This script has to run with local admin credentials. ie:
.\Administrator
1234
The user witch will execute the exe also has it's own credentials, ie:
DOMAIN\User
1234
How can I manage this scenario?
I've successfully connected to the smb with proper credentials with this class:
using System;
using System.Runtime.InteropServices;
using BOOL = System.Boolean;
using DWORD = System.UInt32;
using LPWSTR = System.String;
using NET_API_STATUS = System.UInt32;
namespace blah
{
class UNCAccess
{
// FROM: https://ericwijaya.wordpress.com/2013/02/06/access-remote-file-share-with-username-and-password-in-c/
//
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern NET_API_STATUS NetUseAdd(
LPWSTR UncServerName,
DWORD Level,
ref USE_INFO_2 Buf,
out DWORD ParmError);
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern NET_API_STATUS NetUseDel(
LPWSTR UncServerName,
LPWSTR UseName,
DWORD ForceCond);
private string sUNCPath;
private string sUser;
private string sPassword;
private string sDomain;
private int iLastError;
public UNCAccess()
{
}
public UNCAccess(string UNCPath, string User, string Domain, string Password)
{
login(UNCPath, User, Domain, Password);
}
public int LastError
{
get { return iLastError; }
}
/// <summary>
/// Logs in to the shared network with the provided credentials
/// </summary>
/// <param name="UNCPath">unc</param>
/// <param name="User">user</param>
/// <param name="Domain">domain</param>
/// <param name="Password">password</param>
/// <returns>TRUE OK, ELSE FALSE</returns>
public bool login(string UNCPath, string User, string Domain, string Password)
{
sUNCPath = UNCPath;
sUser = User;
sPassword = Password;
sDomain = Domain;
return NetUseWithCredentials();
}
private bool NetUseWithCredentials()
{
uint returncode;
try
{
USE_INFO_2 useinfo = new USE_INFO_2();
useinfo.ui2_remote = sUNCPath;
useinfo.ui2_username = sUser;
useinfo.ui2_domainname = sDomain;
useinfo.ui2_password = sPassword;
useinfo.ui2_asg_type = 0;
useinfo.ui2_usecount = 1;
uint paramErrorIndex;
returncode = NetUseAdd(null, 2, ref useinfo, out paramErrorIndex);
iLastError = (int)returncode;
return returncode == 0;
}
catch
{
iLastError = Marshal.GetLastWin32Error();
return false;
}
}
///
/// Closes the UNC share
///
/// True if closing was successful
public bool NetUseDelete()
{
uint returncode;
try
{
returncode = NetUseDel(null, sUNCPath, 2);
iLastError = (int)returncode;
return (returncode == 0);
}
catch
{
iLastError = Marshal.GetLastWin32Error();
return false;
}
}
}
}
Then I did a process start to run the script as admin:
Process p = new Process();
p.StartInfo.FileName = "cscript.exe";
p.StartInfo.WorkingDirectory = #"c:\";
p.StartInfo.Arguments = "//B //Nologo " + script.FullName
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.Verb = "runas";
p.StartInfo.UserName = localAdminAccount;
System.Security.SecureString pwd = new System.Security.SecureString();
foreach (char c in localAdminPasswd) { pwd.AppendChar(c); }
p.StartInfo.Password = pwd;
The problem is that when the process starts with the new credentials (localAdmin) I'm not able to find the script (the user has changed so no access to the shared network).
I though it was because of the user, so I've also tried to create a launcher to elevate the privileges of the execution of the main application without user interaction (another process.start from the launcher), which works fine, but then the same thing happens (not found).
Any help on this? Thanks
I've solved it with two launchers to get the privileges and a runas in the process start, so now i can run the script with the necesary credentials =)
I'm having a problem with my FtpFindFirstFile function on my C# project. Basically this function is just to search the specified directory for a file that I mention in my program, but an error appears right before the function is finish executing, here's the screenshot of the error:
---------------START CODE------------------
[System.Runtime.InteropServices.DllImport("wininet.dll", EntryPoint = "InternetOpen", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr InternetOpen(
string lpszAgent, int dwAccessType, string lpszProxyName,
string lpszProxyBypass, int dwFlags);
[System.Runtime.InteropServices.DllImport("wininet.dll", EntryPoint = "InternetConnect", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
extern public static IntPtr /*IntPtr*/ InternetConnect(
IntPtr hInternet, string lpszServerName, int nServerPort,
string lpszUsername, string lpszPassword, int dwService,
int dwFlags, int dwContext);
[System.Runtime.InteropServices.DllImport("wininet.dll", EntryPoint = "FtpFindFirstFile", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
extern public static IntPtr FtpFindFirstFile(
IntPtr hConnect, string searchFile, out WIN32_FIND_DATA findFileData,
int flags, IntPtr context);
#region WIN32_Structure
public struct WIN32_FIND_DATA
{
public int dwFileAttributes;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
public string cFileName;
public string cAlternateFileName;
}
#endregion
public void PerformFTP(string HostIP, string logUsrName, string LogPwd, string SendType, string DefaultDir, string fileExtension)
{
#region Declaration
WIN32_FIND_DATA win32 = new WIN32_FIND_DATA();
bool pRoceed;
#endregion
pRoceed = true;
/* Initialize Internet Connection */
IntPtr hInternet = InternetOpen("browser", INTERNET_OPEN_TYPE_DIRECT, null, null, 0);
//IntPtr hInternet = InternetOpen("browser", 1, null, null, 0);
if (hInternet == IntPtr.Zero)
{
MessageBox.Show(hInternet.ToString(), "");
MessageBox.Show(System.Runtime.InteropServices.Marshal.GetLastWin32Error().ToString());
}
/* Initialize FTP Connection */
IntPtr hFTPhandle = InternetConnect(hInternet, HostIP, INTERNET_DEFAULT_FTP_PORT, logUsrName, LogPwd, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0);
//IntPtr hFTPhandle = InternetConnect(hInternet, "203.177.252.123", 21, "bomoracle", "bomoracle", 1, 0, 0);
/* To check if the FTP connection succeeded */
if (hFTPhandle == IntPtr.Zero)
{
pRoceed = false;
MessageBox.Show(hFTPhandle.ToString(), "");
MessageBox.Show(System.Runtime.InteropServices.Marshal.GetLastWin32Error().ToString());
return;
}
//IntPtr hFind = FtpFindFirstFile(hFTPhandle, "*.DAT" /*+ fileExtension*/ , out win32, 0, IntPtr.Zero);
IntPtr hFind = FtpFindFirstFile(hFTPhandle, "*.DAT" , out win32, 0, IntPtr.Zero); **//THIS IS WHERE THE ERROR APPEARS**
if (hFind == IntPtr.Zero)
{
if (System.Runtime.InteropServices.Marshal.GetLastWin32Error().ToString() == "RROR_NO_MORE_FILES")
{
MessageBox.Show("NO MORE .BOM FILES","EMPTY");
}
MessageBox.Show("SEARCHING IN THE DIRECTORY FAILED! ", "EMPTY");
}
}
---------------END CODE------------------
Here's the error message, it appears right before executing the if-else condition:
"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
I don't know what's causing the error, I was just searching the directory, I haven't done any get or put command on that ftp process. Hope you can help! Thanks!
I cann't answer your specific question but I strongly feel there is already a managed solution. Remember that you only need to fallback to interop when the framework has no implementation that suits your needs.
var request = (FtpWebRequest)WebRequest.Create("ftp://example.com/");
request.Credentials= new NetworkCredential("username", "password");
// List files
request.Method = WebRequestMethods.Ftp.ListDirectory;
var resp = (FtpWebResponse) request.GetResponse();
var stream = resp.GetResponseStream();
var readStream = new StreamReader(resp.GetResponseStream(), System.Text.Encoding.UTF8);
// handle the incoming stream, store in a List, print, find etc
var files = new List<String>();
if (readStream != null)
{
while(!readStream.EndOfStream)
{
files.Add(readStream.ReadLine());
}
}
// showe them
foreach(var file in files)
{
Console.WriteLine(file);
}
// find one
var fileToFind = "Public";
var foundFile = files.Find( f => f == fileToFind);
Console.WriteLine("found file {0}:", foundFile);
// show status
Console.WriteLine("List status: {0}",resp.StatusDescription);
In this snippet I used:
FtpWebResponse
FtpWebRequest
WebRequestMethods.Ftp
List.Find
StreamReader