// Copyright 2026 sjackson0109 — Apache License 2.0
using System.Runtime.InteropServices;
namespace RDPWrap.Common;
///
/// Security helpers: granting SID-based DACL entries (GrantSidFullAccess) and
/// adjusting process token privileges (AddPrivilege). Mirrors the Delphi
/// implementations in RDPWInst.dpr.
///
public static class SecurityHelper
{
// ── DACL: grant a well-known SID full access to a file/folder ─────────────
///
/// Grants GENERIC_ALL access to the well-known SID string
/// on the file/directory at
/// . Mirrors the Delphi GrantSidFullAccess
/// procedure (used for "S-1-5-18" = Local System, "S-1-5-6" = Service).
///
public static void GrantSidFullAccess(string path, string stringSid)
{
if (!NativeMethods.ConvertStringSidToSid(stringSid, out IntPtr pSid))
{
Console.Error.WriteLine(
$"[-] ConvertStringSidToSid error (code {Marshal.GetLastWin32Error()}) " +
$"for SID {stringSid}.");
return;
}
try
{
var ea = new NativeMethods.EXPLICIT_ACCESS
{
grfAccessPermissions = NativeMethods.GENERIC_ALL,
grfAccessMode = NativeMethods.GRANT_ACCESS,
grfInheritance = NativeMethods.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
Trustee = new NativeMethods.TRUSTEE
{
pMultipleTrustee = IntPtr.Zero,
MultipleTrusteeOperation = NativeMethods.NO_MULTIPLE_TRUSTEE,
TrusteeForm = NativeMethods.TRUSTEE_IS_SID,
TrusteeType = NativeMethods.TRUSTEE_IS_WELL_KNOWN_GROUP,
ptstrName = pSid
}
};
uint result = NativeMethods.SetEntriesInAcl(1, ref ea, IntPtr.Zero, out IntPtr pNewAcl);
if (result == NativeMethods.ERROR_SUCCESS)
{
uint secResult = NativeMethods.SetNamedSecurityInfo(
path,
NativeMethods.SE_FILE_OBJECT,
NativeMethods.DACL_SECURITY_INFORMATION,
IntPtr.Zero, IntPtr.Zero, pNewAcl, IntPtr.Zero);
if (secResult != NativeMethods.ERROR_SUCCESS)
Console.Error.WriteLine(
$"[-] SetNamedSecurityInfo error (code {secResult}).");
NativeMethods.LocalFree(pNewAcl);
}
else
{
Console.Error.WriteLine($"[-] SetEntriesInAcl error (code {result}).");
}
}
finally
{
NativeMethods.LocalFree(pSid);
}
}
// ── Token privileges ──────────────────────────────────────────────────────
///
/// Enables the named privilege (e.g. SeDebugPrivilege) for the
/// current process token. Mirrors the Delphi AddPrivilege function.
/// Returns true on success.
///
public static bool AddPrivilege(string privilegeName)
{
if (!NativeMethods.OpenProcessToken(
System.Diagnostics.Process.GetCurrentProcess().Handle,
NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY,
out IntPtr hToken))
{
Console.Error.WriteLine(
$"[-] OpenProcessToken error (code {Marshal.GetLastWin32Error()}).");
return false;
}
try
{
if (!NativeMethods.LookupPrivilegeValue(null, privilegeName, out var luid))
{
Console.Error.WriteLine(
$"[-] LookupPrivilegeValue error (code {Marshal.GetLastWin32Error()}).");
return false;
}
var tp = new NativeMethods.TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new[]
{
new NativeMethods.LUID_AND_ATTRIBUTES
{
Luid = luid,
Attributes = NativeMethods.SE_PRIVILEGE_ENABLED
}
}
};
if (!NativeMethods.AdjustTokenPrivileges(
hToken, false, ref tp,
(uint)Marshal.SizeOf(tp),
IntPtr.Zero, IntPtr.Zero))
{
Console.Error.WriteLine(
$"[-] AdjustTokenPrivileges error (code {Marshal.GetLastWin32Error()}).");
return false;
}
return true;
}
finally
{
NativeMethods.CloseHandle(hToken);
}
}
// ── RDP-Tcp listener ──────────────────────────────────────────────────────
///
/// Returns true when there is an active "RDP-Tcp" WTS listener on
/// the local machine — i.e. WinStationEnumerateW returns a session
/// named "RDP-Tcp". Mirrors the Delphi IsListenerWorking function.
///
public static bool IsRdpListenerWorking()
{
if (!NativeMethods.WinStationEnumerate(IntPtr.Zero,
out IntPtr ppInfo, out uint count))
return false;
try
{
int ptrSize = IntPtr.Size;
// WTS_SESSION_INFO is: DWORD SessionId + 34 WCHARs (Name) + DWORD State
// = 4 + 68 + 4 = 76 bytes, but aligned to 4-byte boundary → 76 bytes.
int entrySize = 4 + 34 * 2 + 4; // = 76
for (uint i = 0; i < count; i++)
{
IntPtr entry = ppInfo + (int)(i * (uint)entrySize);
// Name starts at offset 4
string name = Marshal.PtrToStringUni(entry + 4, 34).TrimEnd('\0');
if (name == "RDP-Tcp") return true;
}
}
finally
{
NativeMethods.WinStationFreeMemory(ppInfo);
}
return false;
}
}