Detaching console does not flip Environment.UserInteractive to false. Window station does.

This commit is contained in:
2024-09-03 01:20:50 +02:00
parent bc2b9dbbae
commit 6b1a270d56
3 changed files with 306 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
**/bin
**/obj

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,296 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.IO.Pipes;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading;
using System.Collections;
class A
{
private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private const uint CREATE_SUSPENDED = 0x00000004;
private const uint DETACHED_PROCESS = 0x00000008;
private const int STARTF_USESTDHANDLES = 0x00000100;
private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
string lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetExitCodeProcess(IntPtr hProcess, out int lpExitCode);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.U4)]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateWindowStation(string lpwinsta, uint dwReserved, uint dwDesiredAccess, IntPtr lpsa);
private const uint WINSTA_ALL_ACCESS = 0x37F;
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetProcessWindowStation(IntPtr hWinSta);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, uint dwFlags, uint dwDesiredAccess, IntPtr lpsa);
private const uint GENERIC_ALL = 0x10000000;
public static async Task<int> Main(string[] args)
{
Console.WriteLine(string.Format("Is user-interactive: {0}", Environment.UserInteractive));
if (args.Length == 0)
{
Console.Error.WriteLine("Specify executable to run detached.");
return 1;
}
// Propagate the command line
StringBuilder argsSub = new StringBuilder();
for (int i = 0; i < args.Length; i++)
argsSub.Append('"').Append(args[i]).Append('"').Append(' ');
// Propagate environment variables
StringBuilder env = new StringBuilder();
foreach (DictionaryEntry i in Environment.GetEnvironmentVariables())
env.Append((string)i.Key).Append('=').Append((string)i.Value).Append('\0');
IntPtr hSta = CreateWindowStation("detach", 0, WINSTA_ALL_ACCESS, IntPtr.Zero);
if (!SetProcessWindowStation(hSta)) {
Console.Error.WriteLine("Failed to switch to a new windows station.");
return 10;
}
Console.WriteLine(string.Format("Is user-interactive2: {0}", Environment.UserInteractive));
IntPtr hDesktop = CreateDesktop("desktop", IntPtr.Zero, IntPtr.Zero, 0, GENERIC_ALL, IntPtr.Zero);
if (hDesktop == IntPtr.Zero) {
Console.Error.WriteLine("Failed to create a non-interactive desktop.");
return 10;
}
//env.Append("EAZFUSCATOR_NET_LICENSE=").Append("4SY9QB68-3WD55J3Y-XWCD7DFF-UPZVRNXJ-PWFEH4P5-YHJFCGQP-AGTBM5GF-JSK8JN3").Append('\0');
using (AnonymousPipeServerStream fOutW = new AnonymousPipeServerStream(PipeDirection.Out))
using (AnonymousPipeClientStream fOutR = new AnonymousPipeClientStream(PipeDirection.In, fOutW.GetClientHandleAsString()))
using (AnonymousPipeServerStream fErrW = new AnonymousPipeServerStream(PipeDirection.Out))
using (AnonymousPipeClientStream fErrR = new AnonymousPipeClientStream(PipeDirection.In, fErrW.GetClientHandleAsString()))
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.hStdInput = INVALID_HANDLE_VALUE;
si.hStdOutput = fOutW.SafePipeHandle.DangerousGetHandle();
si.hStdError = fErrW.SafePipeHandle.DangerousGetHandle();
si.dwFlags = STARTF_USESTDHANDLES;
//si.lpDesktop = "";// "detach\\desktop"; // does not work
// Start the process detached from the current console and thus losing
// the "interactiveness"
PROCESS_INFORMATION pi;
string executablePath = args[0];
if (!CreateProcess(executablePath, argsSub.ToString(), IntPtr.Zero, IntPtr.Zero, false, DETACHED_PROCESS, env.ToString(), null, ref si, out pi))
{
Console.Error.WriteLine(string.Format("Failed to start process '{0}'.", executablePath));
return 2;
}
using (Process p = Process.GetProcessById(pi.dwProcessId))
{
// Continually copy the standard streams
CancellationTokenSource cancel = new CancellationTokenSource();
Task tCopyOut = CopyStreamAsync(fOutR, Console.OpenStandardOutput(), cancel.Token);
Task tCopyErr = CopyStreamAsync(fErrR, Console.OpenStandardError(), cancel.Token);
p.WaitForExit();
// Complete
cancel.Cancel();
//await Task.WhenAll(tCopyOut, tCopyErr);
int exitCode;
if (!GetExitCodeProcess(pi.hProcess, out exitCode))
{
Console.Error.WriteLine("Failed to get the exit code.");
return 3;
}
return exitCode;
}
}
}
private static async Task CopyStreamAsync(Stream src, Stream dst, CancellationToken cancel)
{
byte[] buffer = new byte[4096];
while (!cancel.IsCancellationRequested)
{
int read = await src.ReadAsync(buffer, 0, buffer.Length, cancel);
await dst.WriteAsync(buffer, 0, read, cancel);
}
}
private static void EnumSessions()
{
UInt64 count;
IntPtr luidPtr = IntPtr.Zero;
LsaEnumerateLogonSessions(out count, out luidPtr); //gets an array of
//pointers to LUIDs
IntPtr iter = luidPtr; //set the pointer to the
//start of the array
for (ulong i = 0; i < count; i++) //for each pointer in the
//array
{
IntPtr sessionData;
LsaGetLogonSessionData(iter, out sessionData);
SECURITY_LOGON_SESSION_DATA data = (
SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure
(sessionData, typeof(SECURITY_LOGON_SESSION_DATA));
//if we have a valid logon
if (data.PSiD != IntPtr.Zero)
{
//get the security identifier for further use
System.Security.Principal.SecurityIdentifier sid =
new System.Security.Principal.SecurityIdentifier(data.PSiD);
SECURITY_LOGON_TYPE secType = (SECURITY_LOGON_TYPE)data.LogonType;
//Look at sectype to see if interactive or remote interactive
Console.WriteLine(secType.ToString());
if (secType == SECURITY_LOGON_TYPE.Interactive || secType == SECURITY_LOGON_TYPE.RemoteInteractive)
{
//do something
}
}
iter = (IntPtr)((int)iter + Marshal.SizeOf(typeof(LUID)));
//move the pointer forward
LsaFreeReturnBuffer(sessionData);
//free the SECURITY_LOGON_SESSION_DATA memory in the struct
}
LsaFreeReturnBuffer(luidPtr); //free the array of LUIDs
}
[DllImport("secur32.dll", SetLastError = false)]
private static extern uint LsaFreeReturnBuffer(IntPtr buffer);
[DllImport("Secur32.dll", SetLastError = false)]
private static extern uint LsaEnumerateLogonSessions
(out UInt64 LogonSessionCount, out IntPtr LogonSessionList);
[DllImport("Secur32.dll", SetLastError = false)]
private static extern uint LsaGetLogonSessionData(IntPtr luid,
out IntPtr ppLogonSessionData);
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public UInt32 LowPart;
public UInt32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_LOGON_SESSION_DATA
{
public UInt32 Size;
public LUID LoginID;
public LSA_UNICODE_STRING Username;
public LSA_UNICODE_STRING LoginDomain;
public LSA_UNICODE_STRING AuthenticationPackage;
public UInt32 LogonType;
public UInt32 Session;
public IntPtr PSiD;
public UInt64 LoginTime;
public LSA_UNICODE_STRING LogonServer;
public LSA_UNICODE_STRING DnsDomainName;
public LSA_UNICODE_STRING Upn;
}
private enum SECURITY_LOGON_TYPE : uint
{
Interactive = 2, //The security principal is logging on
//interactively.
Network, //The security principal is logging using a
//network.
Batch, //The logon is for a batch process.
Service, //The logon is for a service account.
Proxy, //Not supported.
Unlock, //The logon is an attempt to unlock a workstation.
NetworkCleartext, //The logon is a network logon with cleartext
//credentials.
NewCredentials, //Allows the caller to clone its current token and
//specify new credentials for outbound connections.
RemoteInteractive, //A terminal server session that is both remote
//and interactive.
CachedInteractive, //Attempt to use the cached credentials without
//going out across the network.
CachedRemoteInteractive,// Same as RemoteInteractive, except used
// internally for auditing purposes.
CachedUnlock // The logon is an attempt to unlock a workstation.
}
}