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

LSA Functions - Privileges and Impersonation

Rate me:
Please Sign up or sign in to vote.
4.46/5 (31 votes)
27 Aug 2003CPOL2 min read 261.5K   4.3K   64   53
Managing privileges and impersonating users

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.

C#
[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:

C#
[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:

C#
[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:

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

C#
//pointer an size for the SID
IntPtr sid = IntPtr.Zero;
int sidSize = 0; 

//StringBuilder and size for the domain name
StringBuilder domainName = new StringBuilder();
int nameSize = 0;

//account-type variable for lookup
int accountType = 0; 

//get required buffer size
LookupAccountName(String.Empty, accountName, sid, ref sidSize, 
    domainName, ref nameSize, ref accountType); 

//allocate buffers
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);

//lookup the SID for the account
bool result = LookupAccountName(String.Empty, accountName, sid, 
    ref sidSize, domainName, ref nameSize, ref accountType); 

And secondly, open a policy handle.

C#
//initialize an empty unicode-string
LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING(); 

//initialize a pointer for the policy handle
IntPtr policyHandle = IntPtr.Zero; 

//these attributes are not used, but LsaOpenPolicy 
//wants them to exists
LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();

//get a policy handle
uint resultPolicy = LsaOpenPolicy(ref systemName, ref ObjectAttributes, 
    access, out policyHandle);

And finally we are ready to add privileges.

C#
//initialize an unicode-string for the privilege name
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 );

//add the privilege to the account 
long res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
winErrorCode = LsaNtStatusToWinError(res); 
if(winErrorCode != 0)
{ 
    Console.WriteLine("LsaAddAccountRights failed: "+ winErrorCode); 
} 
//close all handles 
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:

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

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

C#
using System.Security.Principal;
//...
WindowsIdentity newId = new WindowsIdentity(duplicateTokenHandle);
WindowsImpersonationContext impersonatedUser = newId.Impersonate();

Of course we have to free the handles at last.

C#
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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions

 
GeneralRe: LsaOpenPolicy Error...again Pin
Jecho Jekov12-Jun-09 9:37
Jecho Jekov12-Jun-09 9:37 
GeneralVery good job Pin
segelfix19-Mar-06 10:48
segelfix19-Mar-06 10:48 
GeneralImpersonation - Console App - SAMBA Pin
lucasfong6-Jan-05 10:55
lucasfong6-Jan-05 10:55 
GeneralLsaOpenPolicy Error Pin
Kermittt15-Sep-04 20:42
Kermittt15-Sep-04 20:42 
GeneralRe: LsaOpenPolicy Error Pin
gav_jackson5-Jan-05 4:24
gav_jackson5-Jan-05 4:24 
GeneralRe: LsaOpenPolicy Error Pin
Kermittt5-Jan-05 11:58
Kermittt5-Jan-05 11:58 
GeneralRe: LsaOpenPolicy Error Pin
gav_jackson5-Jan-05 21:59
gav_jackson5-Jan-05 21:59 
GeneralRe: LsaOpenPolicy Error Pin
Kermittt6-Jan-05 11:49
Kermittt6-Jan-05 11:49 
Sorry bout that, I went off on completely the wrong track there. The code for the class I ended up using is below. I converted Corinna's class to VB.NET (since that's my language of choice) and if I recall correctly the problem went away somewhere during the conversion. If there was something specific I fixed it's too long ago for me to recall what it was now. Hopefully if you compare the 2 you might be able to spot what's different, or maybe you could just compile this into a dll that you can use from your C# projects.

To grant the Logon As A Service right you'd call the SetRight function like this...
Dim lsaResult As Long = LsaFunctions.SetRight("domain\account", "SeServiceLogonRight")
...and a 0 result means it was sucessful.


Here's my VB.NET class, which is basically equivilent to Corinna's LsaUtility class.
(BTW... sorry for the long post everyone, but I can't see any other way to attach this.)
<br />
Imports System.Text<br />
Imports System.Runtime.InteropServices<br />
<br />
Public Class LsaFunctions<br />
<br />
    ' Import the LSA functions<br />
    <DllImport("advapi32.dll", PreserveSig:=True)> _<br />
    Private Shared Function LsaOpenPolicy(ByRef SystemName As LSA_UNICODE_STRING, ByRef ObjectAttributes As LSA_OBJECT_ATTRIBUTES, ByVal DesiredAccess As Int32, ByRef PolicyHandle As IntPtr) As UInt32<br />
    End Function<br />
<br />
    <DllImport("advapi32.dll", SetLastError:=True, PreserveSig:=True)> _<br />
    Private Shared Function LsaAddAccountRights(ByVal PolicyHandle As IntPtr, ByVal AccountSid As IntPtr, ByVal UserRights As LSA_UNICODE_STRING(), ByVal CountOfRights As Long) As UInt32<br />
    End Function<br />
<br />
    <DllImport("advapi32")> _<br />
    Public Shared Sub FreeSid(ByVal pSid As IntPtr)<br />
    End Sub<br />
<br />
    <DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True, PreserveSig:=True)> _<br />
    Private Shared Function LookupAccountName(ByVal lpSystemName As String, ByVal lpAccountName As String, ByVal psid As IntPtr, ByRef cbsid As Int32, ByVal domainName As StringBuilder, ByRef cbdomainLength As Int32, ByRef use As Int32) As Boolean<br />
    End Function<br />
<br />
    <DllImport("advapi32.dll")> _<br />
    Private Shared Function IsValidSid(ByVal pSid As IntPtr) As Boolean<br />
    End Function<br />
<br />
    <DllImport("advapi32.dll")> _<br />
    Private Shared Function LsaClose(ByVal ObjectHandle As IntPtr) As Long<br />
    End Function<br />
<br />
    <DllImport("kernel32.dll")> _<br />
    Private Shared Function GetLastError() As Long<br />
    End Function<br />
<br />
    ' Define the structures<br />
    <StructLayout(LayoutKind.Sequential)> _<br />
    Private Structure LSA_UNICODE_STRING<br />
        Public Length As UInt16<br />
        Public MaximumLength As UInt16<br />
        Public Buffer As IntPtr<br />
    End Structure<br />
<br />
    <StructLayout(LayoutKind.Sequential)> _<br />
    Private Structure LSA_OBJECT_ATTRIBUTES<br />
        Public Length As Int32<br />
        Public RootDirectory As IntPtr<br />
        Public ObjectName As LSA_UNICODE_STRING<br />
        Public Attributes As UInt32<br />
        Public SecurityDescriptor As IntPtr<br />
        Public SecurityQualityOfService As IntPtr<br />
    End Structure<br />
<br />
    ' Enum all policies<br />
    Private Enum LSA_AccessPolicy As Long<br />
        POLICY_VIEW_LOCAL_INFORMATION = &H1L<br />
        POLICY_VIEW_AUDIT_INFORMATION = &H2L<br />
        POLICY_GET_PRIVATE_INFORMATION = &H4L<br />
        POLICY_TRUST_ADMIN = &H8L<br />
        POLICY_CREATE_ACCOUNT = &H10L<br />
        POLICY_CREATE_SECRET = &H20L<br />
        POLICY_CREATE_PRIVILEGE = &H40L<br />
        POLICY_SET_DEFAULT_QUOTA_LIMITS = &H80L<br />
        POLICY_SET_AUDIT_REQUIREMENTS = &H100L<br />
        POLICY_AUDIT_LOG_ADMIN = &H200L<br />
        POLICY_SERVER_ADMIN = &H400L<br />
        POLICY_LOOKUP_NAMES = &H800L<br />
        POLICY_NOTIFICATION = &H1000L<br />
    End Enum<br />
<br />
    ' <summary>Adds a privilege to an account</summary><br />
    ' <param name="accountName">Name of an account - "domain\account" or only "account"</param><br />
    ' <param name="privilegeName">Name ofthe privilege</param><br />
    ' <returns>The windows error code returned by LsaAddAccountRights</returns><br />
    Public Shared Function SetRight(ByVal accountName As String, ByVal privilegeName As String) As Long<br />
        Dim winErrorCode As Long = 0 ' contains the last error<br />
<br />
        ' pointer an size for the SID<br />
        Dim sid As IntPtr = IntPtr.Zero<br />
        Dim sidSize As Int32 = 0<br />
<br />
        ' StringBuilder and size for the domain name<br />
        Dim domainName As StringBuilder = New StringBuilder<br />
        Dim nameSize As Int32 = 0<br />
<br />
        ' account-type variable for lookup<br />
        Dim accountType As Int32 = 0<br />
<br />
        ' get required buffer size<br />
        LookupAccountName(String.Empty, accountName, sid, sidSize, domainName, nameSize, accountType)<br />
<br />
        ' allocate buffers<br />
        domainName = New StringBuilder(nameSize)<br />
        sid = Marshal.AllocHGlobal(sidSize)<br />
<br />
        ' lookup the SID for the account<br />
        Dim result As Boolean = LookupAccountName(String.Empty, accountName, sid, sidSize, domainName, nameSize, accountType)<br />
<br />
        ' say what you're doing<br />
        'Dim msg As String<br />
        'msg &= "LookupAccountName result = " & result & vbCrLf<br />
        'msg &= "IsValidSid: " & IsValidSid(sid) & vbCrLf<br />
        'msg &= "LookupAccountName domainName: " & domainName.ToString()<br />
        'MessageBox.Show(msg)<br />
<br />
        If Not result Then<br />
            winErrorCode = GetLastError()<br />
            MessageBox.Show("LookupAccountName failed: " & winErrorCode, "ServiceUtils")<br />
        Else<br />
            ' initialize an empty unicode-string<br />
            Dim systemName As LSA_UNICODE_STRING = New LSA_UNICODE_STRING<br />
<br />
            ' Combine policies required to grant/deny privileges<br />
            Dim access As Int32 = CInt( _<br />
                LSA_AccessPolicy.POLICY_CREATE_ACCOUNT Or _<br />
                LSA_AccessPolicy.POLICY_LOOKUP_NAMES)<br />
<br />
            ' initialize a pointer for the policy handle<br />
            Dim policyHandle As IntPtr = IntPtr.Zero<br />
<br />
            ' these attributes are not used, but LsaOpenPolicy wants them to exists<br />
            Dim ObjectAttributes As LSA_OBJECT_ATTRIBUTES = New LSA_OBJECT_ATTRIBUTES<br />
            ObjectAttributes.Length = 0<br />
            ObjectAttributes.RootDirectory = IntPtr.Zero<br />
            ObjectAttributes.Attributes = UInt32.Parse("0")<br />
            ObjectAttributes.SecurityDescriptor = IntPtr.Zero<br />
            ObjectAttributes.SecurityQualityOfService = IntPtr.Zero<br />
<br />
            ' get a policy handle<br />
            Dim resultPolicy As UInt32 = LsaOpenPolicy(systemName, ObjectAttributes, access, policyHandle)<br />
<br />
            If Not resultPolicy.ToString = "0" Then<br />
                MessageBox.Show("OpenPolicy failed: " & resultPolicy.ToString, "ServiceUtils")<br />
            Else<br />
                ' Now that we have the SID an the policy,<br />
                ' we can add rights to the account.<br />
<br />
                ' initialize an unicode-string for the privilege name<br />
                Dim userRights(1) As LSA_UNICODE_STRING<br />
                userRights(0) = New LSA_UNICODE_STRING<br />
                userRights(0).Buffer = Marshal.StringToHGlobalUni(privilegeName)<br />
                userRights(0).Length = UInt16.Parse(privilegeName.Length * UnicodeEncoding.CharSize)<br />
                userRights(0).MaximumLength = UInt16.Parse((privilegeName.Length + 1) * UnicodeEncoding.CharSize)<br />
<br />
                ' add the right to the account<br />
                Dim resultRights As UInt32 = LsaAddAccountRights(policyHandle, sid, userRights, 1)<br />
                If Not resultRights.ToString = "0" Then<br />
                    MessageBox.Show("LsaAddAccountRights failed: " & resultRights.ToString, "ServiceUtils")<br />
                End If<br />
<br />
                LsaClose(policyHandle)<br />
            End If<br />
            FreeSid(sid)<br />
        End If<br />
<br />
        Return winErrorCode<br />
    End Function<br />
End Class<br />

GeneralRe: LsaOpenPolicy Error Pin
Tokus17-Jul-06 8:53
Tokus17-Jul-06 8:53 
GeneralRe: LsaOpenPolicy Error Pin
neotussanonis24-May-07 13:13
neotussanonis24-May-07 13:13 
QuestionUndocumented Lsa* Functions?? Pin
Dan Madden26-Aug-04 6:49
Dan Madden26-Aug-04 6:49 
AnswerRe: Undocumented Lsa* Functions?? Pin
Corinna John29-Aug-04 1:19
Corinna John29-Aug-04 1:19 
Generalquestion Pin
lwcotton91917-Aug-04 23:28
lwcotton91917-Aug-04 23:28 
AnswerRe: question Pin
tee_jay23-Mar-06 7:43
tee_jay23-Mar-06 7:43 
GeneralAppDomain Impersonation Pin
chriskoiak16-Mar-04 0:27
chriskoiak16-Mar-04 0:27 
GeneralRe: AppDomain Impersonation Pin
Matt Esterak25-Jan-06 18:33
Matt Esterak25-Jan-06 18:33 
GeneralSID Retrieving Pin
zdenek.smid11-Feb-04 5:50
zdenek.smid11-Feb-04 5:50 
GeneralRe: SID Retrieving Pin
Dan Madden26-Aug-04 6:35
Dan Madden26-Aug-04 6:35 
GeneralImpersonation Pin
DidactSoft23-Jan-04 21:22
DidactSoft23-Jan-04 21:22 
GeneralRe: Impersonation Pin
Corinna John24-Jan-04 1:25
Corinna John24-Jan-04 1:25 
GeneralError When Trying to Impersonate Against the Domain Pin
frosty86753094-Sep-03 9:45
frosty86753094-Sep-03 9:45 
GeneralRe: Error When Trying to Impersonate Against the Domain Pin
Corinna John4-Sep-03 10:27
Corinna John4-Sep-03 10:27 
GeneralRe: Error When Trying to Impersonate Against the Domain Pin
frosty86753094-Sep-03 11:25
frosty86753094-Sep-03 11:25 
GeneralRe: Error When Trying to Impersonate Against the Domain Pin
Corinna John4-Sep-03 19:16
Corinna John4-Sep-03 19:16 
GeneralRe: Error When Trying to Impersonate Against the Domain Pin
frosty86753095-Sep-03 4:53
frosty86753095-Sep-03 4:53 

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.