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.
[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,
Network,
Batch,
Service,
Proxy,
Unlock,
NetworkCleartext,
NewCredentials,
RemoteInteractive,
CachedInteractive,
CachedRemoteInteractive,
CachedUnlock
}
Enumerate and Extract Logon Session Information
Below is the primary code required to enumerate users and extract information.
DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0);
UInt64 count;
IntPtr luidPtr = IntPtr.Zero;
LsaEnumerateLogonSessions(out count, out luidPtr);
IntPtr iter = luidPtr;
for (ulong i = 0; i < count; i++)
{
IntPtr sessionData;
LsaGetLogonSessionData(iter, out sessionData);
SECURITY_LOGON_SESSION_DATA data =(
SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure
(sessionData, typeof(SECURITY_LOGON_SESSION_DATA));
if (data.PSiD != IntPtr.Zero)
{
System.Security.Principal.SecurityIdentifier sid =
new System.Security.Principal.SecurityIdentifier(data.PSiD);
string username = Marshal.PtrToStringUni(
data.Username.buffer).Trim();
string domain = Marshal.PtrToStringUni(
data.LoginDomain.buffer).Trim();
string authpackage = Marshal.PtrToStringUni(
data.AuthenticationPackage.buffer).Trim();
SECURITY_LOGON_TYPE secType = (SECURITY_LOGON_TYPE)data.LogonType;
DateTime time = systime.AddTicks((long)data.LoginTime);
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)));
LsaFreeReturnBuffer(sessionData);
}
LsaFreeReturnBuffer(luidPtr);
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;
System.Security.Principal.WindowsIdentity currentUser =
System.Security.Principal.WindowsIdentity.GetCurrent();
...
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)
{
}
}
...
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.
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.