Introduction
Sometimes you want your application to do things which the user himself may
never do. For example, your application has to read a public folder on an
exchange server, but the folder is hidden from the active user for good reasons.
Now you need LSA functions, to manage privileges and impersonate another user.
This article explains how to import the LSA functions, add rights to accounts
and impersonate different users.
SIDs, Policies and Rights
Whenever you alter the privileges of an account, you need its
Security Identifier (SID). You can find any account using
LookupAccountName
.
[DllImport( "advapi32.dll", CharSet=CharSet.Auto,
SetLastError=true, PreserveSig=true)]
private static extern bool LookupAccountName(
string lpSystemName, string lpAccountName,
IntPtr psid, ref int cbsid,
StringBuilder domainName, ref int cbdomainLength,
ref int use );
Before adding or removing any privileges, we need a policy handle.
LsaOpenPolicy
opens a handle:
[DllImport("advapi32.dll", PreserveSig=true)]
private static extern UInt32 LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
Int32 DesiredAccess,
out IntPtr PolicyHandle );
Using the SID and the policy handle, LsaAddAccountRights
can add
privileges:
[DllImport("advapi32.dll", SetLastError=true, PreserveSig=true)]
private static extern long LsaAddAccountRights(
IntPtr PolicyHandle, IntPtr AccountSid,
LSA_UNICODE_STRING[] UserRights,
long CountOfRights );
The LSA functions work with Unicode strings, so we have to use the
LSA_UNICODE_STRING
structure. This structure contains a buffer for
the string, an two integers for the length of the buffer and the length of the
actual string in the buffer:
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
Now it's time to call these functions. First, find the desired account and
retrieve the SID.
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
int accountType = 0;
LookupAccountName(String.Empty, accountName, sid, ref sidSize,
domainName, ref nameSize, ref accountType);
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
bool result = LookupAccountName(String.Empty, accountName, sid,
ref sidSize, domainName, ref nameSize, ref accountType);
And secondly, open a policy handle.
LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING();
IntPtr policyHandle = IntPtr.Zero;
LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
uint resultPolicy = LsaOpenPolicy(ref systemName, ref ObjectAttributes,
access, out policyHandle);
And finally we are ready to add privileges.
LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
userRights[0] = new LSA_UNICODE_STRING();
userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
userRights[0].Length = (UInt16)( privilegeName.Length *
UnicodeEncoding.CharSize );
userRights[0].MaximumLength = (UInt16)( (privilegeName.Length+1) *
UnicodeEncoding.CharSize );
long res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
winErrorCode = LsaNtStatusToWinError(res);
if(winErrorCode != 0)
{
Console.WriteLine("LsaAddAccountRights failed: "+ winErrorCode);
}
LsaClose(policyHandle);
FreeSid(sid);
More LSA
Now we can manage user's privileges - but how about being another user? LSA
includes a set of functions to impersonate any user. This means, performing an
invisible logon an switch between our own identity and the new one.
For example, if you're writing a service and you don't get along with network
access of the local service authority, you can define a special domain
account for your service and impersonate it at runtime. LogonUser
is the function to authenticate a user against a domain:
[DllImport("advapi32.dll")]
private static extern bool LogonUser(
String lpszUsername,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken );
LogonUser
verifies the logon parameters an creates a security
token. Whenever a user logs onto a workstation, a security token is created. All
applications launched by this user hold a copy of this token. He have to
copy our new token using DuplicateToken
.
[DllImport("advapi32.dll")]
private static extern bool DuplicateToken(
IntPtr ExistingTokenHandle,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle );
Now we got a copy of the security token, we can create
a WindowsIdentity
and impersonate the user. The .NET framework
contains classes for impersonating users, once we got the right token.
using System.Security.Principal;
WindowsIdentity newId = new WindowsIdentity(duplicateTokenHandle);
WindowsImpersonationContext impersonatedUser = newId.Impersonate();
Of course we have to free the handles at last.
if (existingTokenHandle != IntPtr.Zero)
{
CloseHandle(existingTokenHandle);
}
if (duplicateTokenHandle != IntPtr.Zero)
{
CloseHandle(duplicateTokenHandle);
}
When we have finished the special tasks, we can switch back to our normal
identity
impersonatedUser.Undo();
Using the code
In the LogonDemo project there are LsaUtility.cs and
LogonUtility.cs. LsaUtility.cs imports the functions necessary for
managing privileges and contains the static method SetRight (String
accountName, String privilegeName)
. It adds a named privilege to an
account. LogonUtility.cs contains everything you need to impersonate a
user.