2
votes

Considering there is a running process (C#/.NET, running as standard user - without any administrative privileges) on a Windows machine I would like that process to check how the logon session has started - as Terminal Services/RDP or as an interactive logon to the console?

All I could find is about the current state of the session. But I'm not so much interested about its current state (which can change) but rather about the way it has started.

I have spent some time digging on this but couldn't find anything useful.

Update 1 (not working)

I decided to try out an approach with searching for "rdp" like parent process and came up with following snippet:

public static void ShowParentsToRoot()
{
  var process = Process.GetCurrentProcess();

  while (process != null)
  {
    Console.WriteLine($"Process: processID = {process.Id}, processName = {process.ProcessName}");
    process = ParentProcessUtilities.GetParentProcess(process.Id);
  }
}

I have borrowed GetParentProcess code from here.

And it does not work. When started in interactive console session it gives me something like:

Process: processID = 11836, processName = My.App
Process: processID = 27800, processName = TOTALCMD64
Process: processID = 6092, processName = explorer

Result is the same (PIDs are different of course) if started in new (new login, different user) mstsc session. Any suggestions?

Update 2 (working)

Solution proposed by RbMm is exactly what was needed. Original code was in C++ so I took an effort to provide a C# version.

  class StackSample
  {
    [Flags]
    public enum TokenAccess : uint
    {
      STANDARD_RIGHTS_REQUIRED = 0x000F0000,
      TOKEN_ASSIGN_PRIMARY = 0x0001,
      TOKEN_DUPLICATE = 0x0002,
      TOKEN_IMPERSONATE = 0x0004,
      TOKEN_QUERY = 0x0008,
      TOKEN_QUERY_SOURCE = 0x0010,
      TOKEN_ADJUST_PRIVILEGES = 0x0020,
      TOKEN_ADJUST_GROUPS = 0x0040,
      TOKEN_ADJUST_DEFAULT = 0x0080,
      TOKEN_ADJUST_SESSIONID = 0x0100,

      TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
                         TOKEN_ASSIGN_PRIMARY |
                         TOKEN_DUPLICATE |
                         TOKEN_IMPERSONATE |
                         TOKEN_QUERY |
                         TOKEN_QUERY_SOURCE |
                         TOKEN_ADJUST_PRIVILEGES |
                         TOKEN_ADJUST_GROUPS |
                         TOKEN_ADJUST_DEFAULT |
                         TOKEN_ADJUST_SESSIONID
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

    public enum TOKEN_INFORMATION_CLASS : uint
    {
      TokenUser = 1,
      TokenGroups,
      TokenPrivileges,
      TokenOwner,
      TokenPrimaryGroup,
      TokenDefaultDacl,
      TokenSource,
      TokenType,
      TokenImpersonationLevel,
      TokenStatistics,
      TokenRestrictedSids,
      TokenSessionId,
      TokenGroupsAndPrivileges,
      TokenSessionReference,
      TokenSandBoxInert,
      TokenAuditPolicy,
      TokenOrigin
    }

    public enum TOKEN_TYPE : uint
    {
      TokenPrimary = 0,
      TokenImpersonation
    }

    public enum SECURITY_IMPERSONATION_LEVEL : uint
    {
      SecurityAnonymous = 0,
      SecurityIdentification,
      SecurityImpersonation,
      SecurityDelegation
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
      public UInt32 LowPart;
      public UInt32 HighPart;
    }

    [StructLayout(LayoutKind.Explicit, Size = 8)]
    public struct LARGE_INTEGER
    {
      [FieldOffset(0)] public Int64 QuadPart;
      [FieldOffset(0)] public UInt32 LowPart;
      [FieldOffset(4)] public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TOKEN_STATISTICS
    {
      public LUID TokenId;
      public LUID AuthenticationId;
      public LARGE_INTEGER ExpirationTime;
      public TOKEN_TYPE TokenType;
      public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
      public UInt32 DynamicCharged;
      public UInt32 DynamicAvailable;
      public UInt32 GroupCount;
      public UInt32 PrivilegeCount;
      public LUID ModifiedId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LSA_UNICODE_STRING
    {
      public UInt16 Length;
      public UInt16 MaximumLength;
      public IntPtr buffer;
    }

    [StructLayout(LayoutKind.Sequential)]
    public 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 SECURITY_LOGON_TYPE 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;
    }

    public 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.
    }


    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool GetTokenInformation(
      IntPtr tokenHandle,
      TOKEN_INFORMATION_CLASS tokenInformationClass,
      IntPtr tokenInformation,
      int tokenInformationLength,
      out int returnLength);


    [DllImport("secur32.dll")]
    public static extern uint LsaFreeReturnBuffer(IntPtr buffer);

    [DllImport("Secur32.dll")]
    public static extern uint LsaGetLogonSessionData(IntPtr luid, out IntPtr ppLogonSessionData);


    public static String GetLogonType()
    {
      if (!OpenProcessToken(Process.GetCurrentProcess().Handle, (uint)TokenAccess.TOKEN_QUERY, out var hToken))
      {
        var lastError = Marshal.GetLastWin32Error();
        Debug.Print($"Unable to open process handle. {new Win32Exception(lastError).Message}");

        return null;
      }

      int tokenInfLength = 0;
      GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, IntPtr.Zero, tokenInfLength, out tokenInfLength);
      if (Marshal.SizeOf<TOKEN_STATISTICS>() != tokenInfLength)
      {
        var lastError = Marshal.GetLastWin32Error();
        Debug.Print($"Unable to determine token information struct size. {new Win32Exception(lastError).Message}");

        return null;
      }

      IntPtr pTokenInf = Marshal.AllocHGlobal(tokenInfLength);

      if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenStatistics, pTokenInf, tokenInfLength, out tokenInfLength))
      {
        var lastError = Marshal.GetLastWin32Error();
        Debug.Print($"Unable to get token information. {new Win32Exception(lastError).Message}");
        Marshal.FreeHGlobal(pTokenInf);

        return null;
      }

      uint getSessionDataStatus = LsaGetLogonSessionData(IntPtr.Add(pTokenInf, Marshal.SizeOf<LUID>()), out var pSessionData);
      Marshal.FreeHGlobal(pTokenInf);

      if (getSessionDataStatus != 0)
      {
        Debug.Print("Unable to get session data.");

        return null;
      }

      var logonSessionData = Marshal.PtrToStructure<SECURITY_LOGON_SESSION_DATA>(pSessionData);

      LsaFreeReturnBuffer(pSessionData);

      if (Enum.IsDefined(typeof(SECURITY_LOGON_TYPE), logonSessionData.LogonType))
      {
        return logonSessionData.LogonType.ToString();
      }

      Debug.Print("Could not determine logon type.");

      return null;
    }
  }

Thanks!

1
By check the parent process ID. Get a list of Processes and check if your process parent is rdp.jdweng
@jdweng - if your process parent is rdp - if you mean mstsc.exe - this is absolute unrelated to rdp session. rdp client can run not on windows also. and in any case not tdp client exec processes in rdp sessionRbMm
@jdweng -parent can already exit. this is not way at all. however initial logon type saved in SECURITY_LOGON_SESSION_DATARbMm
at all process chain here winlogon - userinit - explorer.. nothing related to rdp services. you nothing get here by query parentRbMm
call GetParentProcess absolute senseless here. i give you exact and working codeRbMm

1 Answers

0
votes

for check initial state of session (how it was initial started) we can call LsaGetLogonSessionData - it return SECURITY_LOGON_SESSION_DATA where LogonType a SECURITY_LOGON_TYPE value that identifies the logon method. if session started by RDP - you got RemoteInteractive here. LUID that identifies the logon session you can get from self token inside TOKEN_STATISTICS structure - AuthenticationId

HRESULT GetLogonType(SECURITY_LOGON_TYPE& LogonType )
{
    HANDLE hToken;
    HRESULT hr;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
        ULONG rcb;
        TOKEN_STATISTICS ts;

        hr = GetTokenInformation(hToken, ::TokenStatistics, &ts, sizeof(ts), &rcb) ? S_OK : HRESULT_FROM_WIN32(GetLastError());

        CloseHandle(hToken);

        if (hr == S_OK)
        {
            PSECURITY_LOGON_SESSION_DATA LogonSessionData;

            hr = HRESULT_FROM_NT(LsaGetLogonSessionData(&ts.AuthenticationId, &LogonSessionData));

            if (0 <= hr)
            {
                LogonType = static_cast<SECURITY_LOGON_TYPE>(LogonSessionData->LogonType);
                LsaFreeReturnBuffer(LogonSessionData);
            }
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}

note that this will be work even from restricted, low integrity process.

BOOLEAN IsRemoteSession;

SECURITY_LOGON_TYPE LogonType;
if (0 <= GetLogonType(LogonType))
{
    IsRemoteSession = LogonType == RemoteInteractive;
}