Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

Using the Local Security Authority to Enumerate User Sessions in .NET

Rate me:
Please Sign up or sign in to vote.
4.17/5 (6 votes)
28 Mar 20073 min read 72.4K   1.2K   30   5
An article on enumerating Windows user logon sessions in .NET

Introduction

The Local Security Authority, or LSA, has been included in the Windows operating systems since Windows 2000. LSA is responsible for account validation, management of local security policy, auditing, maintaining sessions and the generation of tokens for login and impersonation. At times you may want to determine what users are logged onto a machine, be it local, remote or system service accounts. LSA maintains a range of information, including usernames, domain information, login times, the authentication package used, SIDs and terminal services session information. Unfortunately, .NET doesn't provide a simple way to interact with LSA, so you have to Interop with the LSA functions directly to enumerate user sessions and extract data.

LSA User Session Enumeration

To get a list of all the current unique logins on a machine, LSA provides the LsaEnumerateLogonSessions function. This returns a pointer to an array of LUIDs, or locally unique identifiers. You need to iterate through and marshal this array to get access to the LUIDs. For each LUID, a call to LsaGetLogonSessionData will enable the extraction of all the login session information into the SECURITY_LOGON_SESSION_DATA struct.

Interop

Most of the interop declarations are straight forward. However, you will need to explicitly marshal most of the data out of the SECURITY_LOGON_SESSION_DATA struct to retrieve useful information. Below are the interop declarations required.

C#
[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.
}

Enumerate and Extract Logon Session Information

Below is the primary code required to enumerate users and extract information.

C#
DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate

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);

        //extract some useful information from the session data struct
        string username = Marshal.PtrToStringUni(
            data.Username.buffer).Trim();
                                                  //get the account username
        string domain =  Marshal.PtrToStringUni(
            data.LoginDomain.buffer).Trim();
                                                   //domain for this account
        string authpackage = Marshal.PtrToStringUni(
            data.AuthenticationPackage.buffer).Trim();
                                                    //authentication package

        SECURITY_LOGON_TYPE secType = (SECURITY_LOGON_TYPE)data.LogonType;
        DateTime time = systime.AddTicks((long)data.LoginTime);
        //get the datetime the session was logged in

        //do something with the extracted data, ie, add to a display control
        listBox1.Items.Add(
          "User: " + username + " *** Domain: " + domain + " *** Login Type:
      (" + data.LogonType + ") " + secType.ToString()
          +" *** Login Time: "+time.ToLocalTime().ToString());

    }
    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

SecurityIdentifier

Once you have populated the SECURITY_LOGON_SESSION_DATA, you can create a SecurityIdentifier class. This is the .NET representation of the SID, and when combined with other .NET classes, enables you to compare users, enum permissions, determine which user is executing a process, as well as other related tasks in the System.Security.Principal namespace. For example, to test if an enumerated user is the current interactive user on the machine;

C#
System.Security.Principal.WindowsIdentity currentUser =
         System.Security.Principal.WindowsIdentity.GetCurrent();
...
//if we have a valid logon
if (data.PSiD != IntPtr.Zero)
{
     System.Security.Principal.SecurityIdentifier sid =
         new System.Security.Principal.SecurityIdentifier(data.PSiD);
     if (sid.CompareTo(currentUser.User) == 0 &&
         data.LogonType == (uint)SECURITY_LOGON_TYPE.Interactive)
     {
         // this is the current interactive user
         // be aware the same SID can be shared simultaneously by
         //multiple logon sessions (for the same user, but in
         //different logon modes)
     }
 }
 ...

Points of Interest

Some of the interop used in this process is quite complex, hence my decision to post this code for others to use. The marshalling and iteration through arrays-of-pointers-to-structs is complex to understand if you haven't had experience in C/C++, especially with array and pointer manipulation. Trying to then convert that into interop can become a little daunting.

The signed and unsigned integer and long data types in the structs were difficult to match to the definitions in Ntsecapi.h, and don't necessarily align to the suggested marshalling types. I have run this through FxCop, tested it under the 32 and 64 bit .NET Frameworks, and it executes correctly. I suggest caution if you intend to change the struct definitions.

Conclusion

The LSA functions are very powerful, however almost none of that functionality is exposed natively in the .NET Framework classes. This example illustrates some of the abilities of LSA, and how to Interop with LSA's functions and structures. There are also a range of good articles on codeproject covering other aspects of LSA, so have a look around for examples of impersonisation and terminal session manipulation.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Australia Australia
Started my programming career working in C++ and Domino before heading out to start my own company with some old University buddies.

Spend most of my time managing staff, but when able to I focus on database architecture and .NET development.

Comments and Discussions

 
QuestionLast Password Change? Pin
MJGelinas5-Aug-14 8:24
MJGelinas5-Aug-14 8:24 
QuestionLogged off users are also returned... Pin
Strider_am24-Jan-11 17:11
Strider_am24-Jan-11 17:11 
GeneralRun process in terminal session Pin
BobilProject18-Jun-08 6:02
BobilProject18-Jun-08 6:02 
QuestionHow can I get the same information regarding remote computer? Pin
vvvlad14-Nov-07 3:19
vvvlad14-Nov-07 3:19 
GeneralLSA is even older Pin
Jakob Bohm10-Apr-07 0:10
Jakob Bohm10-Apr-07 0:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.