How to use ADsSecurity.dll to remotely add local account ACEs to an NTFS file (285998)



The information in this article applies to:

  • Microsoft Active Directory Services Interface, System Component
  • Microsoft Active Directory Client Extension, when used with:
    • the operating system: Microsoft Windows NT 4.0

This article was previously published under Q285998

SUMMARY

This article describes how you can remotely add access control entries (ACEs) with local accounts as their trustee to an NTFS file.

MORE INFORMATION

A common desire is to add the local Administrators group for a system to a file's NTFS permissions. If this action is attempted locally, on the same system, the ACE trustee can be set using the local Security Account Manager (SAM) account name form for the trustee.

For example, if a local system has a computer name of "MyLocalBox", the trustee attribute of the IADsAccessControlEntry would be set to "MyLocalBox\Administrators".

Suppose that a user is trying to modify an ACL remotely. ADsSecruty.dll allows a user to obtain the security descriptor (SD) for an NTFS file. If the user tries to add an ACE with a trustee that is a local account for the remote computer, the action of writing this SD back to the remote file will fail because ACE trustees are stored in the access control list (ACL) as binary security identifiers (SID)s, not in textual form. The IADsSecurityDescriptor interface implemented in ADsSecurity.dll will attempt to convert the SAM account name from the current system into its raw SID form, which will obviously fail.

For additional information, click the following article number to view the article in the Microsoft Knowledge Base:

276208 HOWTO: Convert the SDDL form of an SID to a SAM Account Name

Suppose that a user is trying to execute code on computer "MyLocalMachine" to add the local Administrators group for computer "MyRemoteMachine" to an ACL on "MyRemoteMachine". ADsSecurty.Dll can be used to obtain an IADsSecurityDescriptor interface for the remote NTFS file. When the ACE is added to the ACL for the file, the IADsAccessControlEntry::Trustee property must be set to the Security Descriptor Definition Language (SDDL) form of the SID for "MyRemoteMachine\Administrators". This article illustrates how to obtain a raw SID and convert that SID into its SDDL form, and then set the Trustee property to the SDDL form so that the ACE can be added successfully to the remote NTFS file.

How to Use the Visual Basic Code Provided

Place the code from section 1, following, into a .bas file and include this file in your Visual Basic project.

Next, call the Local_Convert_Bin_To_SDDL function. The function will attempt to call the LookUpAccountName against the first argument, searching for the account passed as the second argument.

The returned value will contain the SDDL form of the SID (S-X-XX-XXX-XXX-...). Place this value into the IADsAccessControlEntry::Trustee property.

Section 2.0 contains the Visual Basic code for a Command1_Click handler that illustrates how to instantiate an IADsSecurity object and add an ACE for a local administrator from a remote system.

NOTES:

  • If this code is not executed under the context of an account that has permission over the remote computer's SAM, then this code will fail because the SDDL form of the security identifier would be computed as "S-0-0".
  • The SID for the Local Administrator account is a well known SID and will be same on all systems.

Section 1: Visual Basic SID Conversion Helper Functions

NOTE: The following code does not include any error checking. You must add error checking to ensure that the API executes successfully.
Attribute VB_Name = "Module1"
'***********************Constant Declaration*********************
' Define the sub SID Authority byte arrays.  These arrays are here for completeness along with
' their VC definitions.
'
'#define SECURITY_NULL_SID_AUTHORITY       {0,0,0,0,0,0}
'#define SECURITY_WORLD_SID_AUTHORITY      {0,0,0,0,0,1}
'#define SECURITY_LOCAL_SID_AUTHORITY      {0,0,0,0,0,2}
'#define SECURITY_CREATOR_SID_AUTHORITY    {0,0,0,0,0,3}
'#define SECURITY_NON_UNIQUE_AUTHORITY     {0,0,0,0,0,4}
'
' C Declaration for the SID memory, included for completeness...
'
'typedef struct _SID {
'  BYTE  Revision;
'  BYTE  SubAuthorityCount;
'   SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
'  DWORD SubAuthority[ANYSIZE_ARRAY];
'} SID;
'typedef PVOID PSID;
'***************End Constant Declaration******************************

'**********************************************************************************
'
' Declare the APIs needed to manipulate the RAW SID
'
Public Declare Function InitializeSid Lib "advapi32.dll" (ByVal Sid As Long, _
                                                          pIndentifierAuthority As Any, _
                                                          ByVal nSubAuthorityCount As Byte) As Long
                                                          
Public Declare Function GetSidSubAuthority Lib "advapi32.dll" (pSid As Any, _
                                                               ByVal nSubAuthority As Long) As Long
                                                               
Public Declare Function GetSidLengthRequired Lib "advapi32.dll" (ByVal nSubAuthorityCount As Byte) As Long

Public Declare Function LookupAccountSid Lib "advapi32.dll" Alias "LookupAccountSidA" _
                                        (ByVal lpSystemName As String, _
                                         Sid As Any, _
                                         ByVal name As String, _
                                         cbName As Long, _
                                         ByVal ReferencedDomainName As String, _
                                         cbReferencedDomainName As Long, _
                                         peUse As Integer) As Long
                                         
Public Declare Function GetSidSubAuthorityCount Lib "advapi32.dll" (pSid As Any) As Long

Public Declare Function GetSidIdentifierAuthority Lib "advapi32.dll" (pSid As Any) As Long
'
' Declare the memory managment functions.
' Notice there are two definitions for RtlMoveMemory, one takes a value as  Any, the other takes a value ByVal as long
' this is necessary so memory can be copied as follows:
'  From a variable containing a 32 bit value that represents a memory location
'  From a variable that represents a VB alocated memory location
'  (ie: Dim bByte(6) as Byte type of declaration).
'
' LocalAlloc and LocalFree are used to allocate and free memory from the heap,
' respectively.
'
Public Declare Sub CopyByValMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
                                                                         ByVal Source As Long, _
                                                                         ByVal Length As Long)
                                                                         
Public Declare Sub CopyByRefMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, _
                                                                         Source As Any, _
                                                                         ByVal Length As Long)
                                                                         
Public Declare Function LocalAlloc Lib "kernel32" (ByVal wFlags As Long, _
                                                   ByVal wBytes As Long) As Long
                                                   
Public Declare Function LocalFree Lib "kernel32" (ByVal hMem As Long) As Long

'************API Function Declarations*************************************
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'
' Declare the necessary Win32 APIs to obtain the SID for the NT user
' LookupAccountName -> Does the real work once it is remoted to the appropriated DC
' NetGetDcName -> Obtains the DC for the local box, used to obtain a DC name for
'                 LookUpAccountName
' NetApiBufferFree-> used to cleanup after NetGetDcName.  The Net* APIs manage memory
'                    memory for you, this function is used to free the memory allocated
'                    NetGetDcName
' lstrcpyW -> used to copy the NetGetDcName buffer into a buffer VB can work with
' GetLengthSid -> used to help convert the raw SID into a hexstring SID
'
Public Declare Function LookupAccountName Lib "advapi32.dll" _
        Alias "LookupAccountNameA" _
        (ByVal IpSystemName As String, _
         ByVal IpAccountName As String, _
         pSid As Byte, _
         cbSid As Long, _
         ByVal ReferencedDomainName As String, _
         cbReferencedDomainName As Long, _
         peUse As Integer) As Long
         
Public Declare Function NetGetDCName Lib "NETAPI32.DLL" _
        (ServerName As Byte, _
         DomainName As Byte, _
         DCNPtr As Long) As Long
                                         
Public Declare Function NetApiBufferFree Lib "NETAPI32.DLL" _
        (ByVal Ptr As Long) As Long
         
Public Declare Function PtrToStr Lib "kernel32" _
        Alias "lstrcpyW" (RetVal As Byte, ByVal Ptr As Long) As Long
       
Public Declare Function GetLengthSid Lib "advapi32.dll" _
        (pSid As Byte) As Long

'************End API Function Declarations**************************
Sub main()
    Form1.Show
End Sub
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' Convert_BIN_To_SDDL takes two arguments: strNTDomain-> The NT domain for the user's account
'                                          strNTAccount-> The NT SAM account name for the user
' The function returns the SDDL form of the user's SID,
'
Public Function Convert_BIN_To_SDDL(strNTDomain As String, strNTAccount As String) As String ' pSid As pSid, pszSidText)
    Dim SDDL_SID As String
    Dim pSia As Long
    Dim pSiaByte(5) As Byte
    Dim pSid(512) As Byte
    Dim pSubAuthorityCount As Long
    Dim bSubAuthorityCount As Byte
    Dim pAuthority As Long
    Dim dAuthority as Double
    Dim lAuthority As Long
    '
    ' Get the SID for the user's account, targetting a local PDC
    '
    IReturn = LookupAccountName(Get_Primary_DCName("", strNTDomain), strNTAccount, pSid(0), 512, pDomain, 512, 1)
    '
    ' Convert the raw sid into its SDDL form ( S-?-?-???-?????? )
    ' The first item in the S- format is the revision level.  If we look closely at the
    ' SID structure in the WinNT.H C Header, we find that the revision value for the SID is
    ' stored in the 0th byte to the raw sid.
    '
    ' Another interesting fact is that the last byte of the Identifying authority structure contains
    ' the second component of the SDDL form, so lets retrieve both of those and
    ' place them into the string.
    '
    pSia = GetSidIdentifierAuthority(pSid(0))
    '
    ' The GetSidIdentifierAuthority returns a pointer to the Identifying Authority structure
    ' The pointer must be copied into some memory that VB knows how to manage, so....
    '
    CopyByValMemory pSiaByte(0), pSia, 6
    SDDL_SID = "S-" + LTrim(str(pSid(0))) + "-" + LTrim(str(pSiaByte(5)))    '
    '
    ' The rest of the SDDL form contains a list of sub authorities separated by
    ' "-"s.  The total number of these authorities can be obtained by
    ' calling the GetSidSubAuthorityCount.  The value returned is a pointer into the
    ' SID memory that contains the Sub Authority value, once again, this memory
    ' must be copied into something that VB knows how to manage.
    '
    ' Notice that only 1 byte is copied.  This is because the sub authority count
    ' is stored in a single byte ( see the SID srtructure definition above )
    '
    pSubAuthorityCount = GetSidSubAuthorityCount(pSid(0))
    CopyByValMemory bSubAuthorityCount, pSubAuthorityCount, 1
    '
    ' We can loop throught the sub authorities and convert
    ' their DWORD values to VB longs, then convert them to a
    ' string.
    ' The count is 0 based, so we start at 0 and goto the
    ' number of sub authorities - 1
    '
    For AuthCount = 0 To bSubAuthorityCount - 1
      pAuthority = GetSidSubAuthority(pSid(0), AuthCount)
      CopyByValMemory lAuthority, pAuthority, LenB(lAuthority)
      '      
      ' VB does not have an unsigned type, the sub authority value
      ' could have the most significant bit set.  If it is set,
      ' the Str function will return a negative number.  Therefore
      ' we must account for this case by converting the value to
      ' a double then to its string form
      ' 
      ' Step 1: Test the bit if set, Do step 2 and 3 else put 
      ' the value in the output string.
      ' Step 2: 1 is true, so, AND off all the bits save the most 
      '   significant bit, place in a temp double variable
      ' Step 3: Add the most significant bit to the double variable ( 2^31)
      ' Output the string
      '
      ' Step 1:
      '
      dAuthority = lAuthority
      if( (lAuthority AND &H80000000) <> 0 ) then
         '
         ' Bit is set, Step 2:
         '
         dAuthority = lAuthority & &H7FFFFFFF
         '
         ' Step 3:
         '
         dAuthority = dAuthority + 2^31
      end if
      SDDL_SID = SDDL_SID + "-" + LTrim(str(dAuthority))
    Next AuthCount
    '
    ' We are done, the SDDL_SID variable contains the SID in
    ' SDDL form,
    ' Return it...
    '
    Convert_BIN_To_SDDL = SDDL_SID
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''
'' Get_Primary_DCName -- gets the name of the Primary Domain Controller for
''                       the NT domain
''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function Get_Primary_DCName(ByVal MName As String, ByVal DName As String) As String

Dim Result As Long
Dim DCName As String
Dim DCNPtr As Long
Dim DNArray() As Byte
Dim MNArray() As Byte
Dim DCNArray(100) As Byte

    MNArray = MName & vbNullChar
    DNArray = DName & vbNullChar
    Result = NetGetDCName(MNArray(0), DNArray(0), DCNPtr)
    If Result <> 0 Then
        Exit Function
    End If
    Result = PtrToStr(DCNArray(0), DCNPtr)
    Result = NetApiBufferFree(DCNPtr)
    DCName = DCNArray()
    Get_Primary_DCName = DCName
    
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''
'' Convert_ObjectSID_To_SDDL converts the objectSID property of a user
'' into the SDDL form of the SID.
''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function Convert_ObjectSID_To_SDDL(obj As Variant) As String
    Dim SDDL_SID As String
    Dim pSia As Long
    Dim pSiaByte(5) As Byte
    Dim pSid(512) As Byte
    Dim pSubAuthorityCount As Long
    Dim bSubAuthorityCount As Byte
    Dim pAuthority As Long
    Dim lAuthority As Long
    Dim dAuthority as Double
    Dim cbpSid As Long
    '
    ' Move the obj array into a byte array to work with the raw SID
    ' Not sure why we need to do this, but if we use the variant passed
    ' as the argument, the GetSidSubAuthorityCount API does not return a valid
    ' address... Curious... It might be a typing issue, the obj property is
    ' defined as a variant array of bytes were as the pSid array is defined as
    ' an array of bytes.
    '
    cbpSid = 0
    For I = LBound(obj) To UBound(obj)
       pSid(cbpSid) = obj(I)
       cbpSid = cbpSid + 1
    Next I
    '
    ' Convert the raw sid into its SDDL form ( S-?-?-???-?????? )
    ' The first item in the S- format is the revision level.  If we look closely at the
    ' SID structure in the WinNT.H C Header, we find that the revision value for the SID is
    ' stored in the 0th byte to the raw sid.
    '
    ' Another interesting fact is that the last byte of the Identifying authority structure contains
    ' the second component of the SDDL form, so lets retrieve both of those and
    ' place them into the string.
    '
    pSia = GetSidIdentifierAuthority(pSid(0))
    '
    ' The GetSidIdentifierAuthority returns a pointer to the Identifying Authority structure
    ' The pointer must be copied into some memory that VB knows how to manage, so....
    '
    CopyByValMemory pSiaByte(0), pSia, 6
    SDDL_SID = "S-" + LTrim(str(pSid(0))) + "-" + LTrim(str(pSiaByte(5)))
    '
    ' The rest of the SDDL form contains a list of sub authorities separated by
    ' "-"s.  The total number of these authorities can be obtained by
    ' calling the GetSidSubAuthorityCount.  The value returned is a pointer into the
    ' SID memory that contains the Sub Authority value, once again, this memory
    ' must be copied into something that VB knows how to manage.
    '
    ' Notice that only 1 byte is copied.  This is because the sub authority count
    ' is stored in a single byte ( see the SID srtructure definition above )
    '
    pSubAuthorityCount = GetSidSubAuthorityCount(pSid(0))
    CopyByValMemory bSubAuthorityCount, pSubAuthorityCount, 1
    '
    ' We can loop throught the sub authorities and convert
    ' their DWORD values to VB longs, then convert them to a
    ' string.
    ' The count is 0 based, so we start a 0 and goto the
    ' number of sub authorities - 1
    '
    For AuthCount = 0 To bSubAuthorityCount - 1
      pAuthority = GetSidSubAuthority(pSid(0), AuthCount)
      CopyByValMemory lAuthority, pAuthority, LenB(lAuthority)
      '      
      ' VB does not have an unsigned type, the sub authority value
      ' could have the most significant bit set.  If it is set,
      ' the Str function will return a negative number.  Therefore
      ' we must account for this case by converting the value to
      ' a double then to its string form
      ' 
      ' Step 1: Test the bit if set, Do step 2 and 3 else put 
      ' the value in the output string.
      ' Step 2: 1 is true, so, AND off all the bits save the most 
      '   significant bit, place in a temp double variable
      ' Step 3: Add the most significant bit to the double variable ( 2^31)
      ' Output the string
      '
      ' Step 1:
      '
      dAuthority = lAuthority
      if( (lAuthority AND &H80000000) <> 0 ) then
         '
         ' Bit is set, Step 2:
         '
         dAuthority = lAuthority & &H7FFFFFFF
         '
         ' Step 3:
         '
         dAuthority = dAuthority + 2^31
      end if
      SDDL_SID = SDDL_SID + "-" + LTrim(str(dAuthority))
    Next AuthCount
    '
    ' We are done, the SDDL_SID variable contains the SID in
    ' SDDL form,
    ' Return it...
    '
    Convert_ObjectSID_To_SDDL = SDDL_SID
    '
End Function
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' Local_Convert_BIN_To_SDDL takes two arguments: strNTDomain-> Netbios name for the NT machine to 
'                                                              Request account information from
'                                          strNTAccount-> The NT SAM account name for the user
' The function returns the SDDL form of the user's SID,
'
Public Function Local_Convert_BIN_To_SDDL(strNTDomain As String, strNTAccount As String) As String ' pSid As pSid, pszSidText)
    Dim SDDL_SID As String
    Dim pSia As Long
    Dim pSiaByte(5) As Byte
    Dim pSid(512) As Byte
    Dim pSubAuthorityCount As Long
    Dim bSubAuthorityCount As Byte
    Dim pAuthority As Long
    Dim lAuthority As Long
    Dim dAuthority as Double
    '
    ' Get the SID for the user's account, targetting a local PDC
    '
    IReturn = LookupAccountName(strNTDomain, strNTAccount, pSid(0), 512, pDomain, 512, 1)
    '
    ' Convert the raw sid into its SDDL form ( S-?-?-???-?????? )
    ' The first item in the S- format is the revision level.  If we look closely at the
    ' SID structure in the WinNT.H C Header, we find that the revision value for the SID is
    ' stored in the 0th byte to the raw sid.
    '
    ' Another interesting fact is that the last byte of the Identifying authority structure contains
    ' the second component of the SDDL form, so lets retrieve both of those and
    ' place them into the string.
    '
    pSia = GetSidIdentifierAuthority(pSid(0))
    '
    ' The GetSidIdentifierAuthority returns a pointer to the Identifying Authority structure
    ' The pointer must be copied into some memory that VB knows how to manage, so....
    '
    CopyByValMemory pSiaByte(0), pSia, 6
    SDDL_SID = "S-" + LTrim(str(pSid(0))) + "-" + LTrim(str(pSiaByte(5)))
    '
    ' The rest of the SDDL form contains a list of sub authorities separated by
    ' "-"s.  The total number of these authorities can be obtained by
    ' calling the GetSidSubAuthorityCount.  The value returned is a pointer into the
    ' SID memory that contains the Sub Authority value, once again, this memory
    ' must be copied into something that VB knows how to manage.
    '
    ' Notice that only 1 byte is copied.  This is because the sub authority count
    ' is stored in a single byte ( see the SID srtructure definition above )
    '
    pSubAuthorityCount = GetSidSubAuthorityCount(pSid(0))
    CopyByValMemory bSubAuthorityCount, pSubAuthorityCount, 1
    '
    ' We can loop throught the sub authorities and convert
    ' their DWORD values to VB longs, then convert them to a
    ' string.
    ' The count is 0 based, so we start a 0 and goto the
    ' number of sub authorities - 1
    '
    For AuthCount = 0 To bSubAuthorityCount - 1
      pAuthority = GetSidSubAuthority(pSid(0), AuthCount)
      CopyByValMemory lAuthority, pAuthority, LenB(lAuthority)
      '      
      ' VB does not have an unsigned type, the sub authority value
      ' could have the most significant bit set.  If it is set,
      ' the Str function will return a negative number.  Therefore
      ' we must account for this case by converting the value to
      ' a double then to its string form
      ' 
      ' Step 1: Test the bit if set, Do step 2 and 3 else put 
      ' the value in the output string.
      ' Step 2: 1 is true, so, AND off all the bits save the most 
      '   significant bit, place in a temp double variable
      ' Step 3: Add the most significant bit to the double variable ( 2^31)
      ' Output the string
      '
      ' Step 1:
      '
      dAuthority = lAuthority
      if( (lAuthority AND &H80000000) <> 0 ) then
         '
         ' Bit is set, Step 2:
         '
         dAuthority = lAuthority & &H7FFFFFFF
         '
         ' Step 3:
         '
         dAuthority = dAuthority + 2^31
      end if
      SDDL_SID = SDDL_SID + "-" + LTrim(str(dAuthority))
    Next AuthCount
    '
    ' We are done, the SDDL_SID variable contains the SID in
    ' SDDL form,
    ' Return it...
    '
    Local_Convert_BIN_To_SDDL = SDDL_SID
End Function
				

Section 2: Visual Basic Code Shows Use of SDDL Helper Functions

Private Sub Command1_Click()
Dim sd As New ADsSecurity
Dim ace As New AccessControlEntry
Dim dacl As AccessControlList
Dim sACLs As SecurityDescriptor
Dim meSD As New ADsSID
Dim strSID As String
'
' Open the files SD
' Be sure to place a path to an NTFS file after the FILE:// text in both,
' the GetSecurityDescritor and SetSecurityDescriptor lines
'
Set sACLs = sd.GetSecurityDescriptor("FILE://\\Server\Share\Temp\me.txt")
Set dacl = sACLs.DiscretionaryAcl
For Each oAce In dacl
  Debug.Print oAce.Trustee
Next oAce
'
' Local_Convert_BIN_To_SDDL takes two arguments, a server name and
' and NT SAM account name.
'
' The function will return the SID in SDDL format.  This string can be
' used to set the trustee attribute of the ACL entry
'
strSID = Local_Convert_BIN_To_SDDL("RemoteMachine", "Administrators")
ace.Trustee = strSID
ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED
ace.AccessMask = ADS_RIGHT_GENERIC_ALL
ace.AceFlags = 3
dacl.AddAce ace
sACLs.DiscretionaryAcl = dacl
sd.SetSecurityDescriptor sACLs, "FILE://\\Server\Share\Temp\me.txt"
End Sub
				

Modification Type:MajorLast Reviewed:5/20/2005
Keywords:kbDSWADSI2003Swept kb32bitOnly kbhowto kbMsg kbSecurity KB285998