How To: Understanding Windows NT Security

Posted: (EET/GMT+2)

 

How To: Understanding Windows NT Security

Level: Intermediate


If you have ever used Windows NT, you know that it has a pretty strong security features. For example, in Windows 95, you can just press Esc when you are requested to enter your password to logon. In NT, you can't do that. But NT's security is much more than just better logon control. For example, every file, registry key, or named object can have security information associated with it. This means that you (as administrator, owner of the object or programmer) can control who has access to the object. This is called access control, and it is the basis of NT's security model.


NT security from the programmers perspective

If you program for Win95, and need to call for example the CreateFile function, you simply pass Nil as the lpSecurityAttributes parameter. In NT, you can continue to do just that. However, in this case you will get default security attributes. However, for advanced applications, you will pass in some parameters to get just the security attributes you want.

The security structure you pass in is usually of type TSecurityAttributes, which is a surprisingly simple record with only three members. The second member is a pointer, and if you investigate futher, you will quickly run into difficult terms like ACLs, groups, tokens and owners. Read on.


Security elements

In NT, security information of an object is stored in a security descriptor. It contains information about the owner of the object (every object must have an owner), and also information about who can access the object and who cannot.

The information what other users can do is stored in an access-control list, or ACL. Note that an "users" means here a individual user, or alternatively a group of users. An ACL can contain multiple elements, and each of the elements is called an access-control entry (ACE). ACEs can etiher grant or deny access.

At logon, a user is assigned an access token containing identifiers that represent the user and any groups to which the user belongs. When a process (application) tries to access an object (resource), NT check to see if the user has appropriate rights to perform the operation. Users and groups are identified by security identifiers (SIDs). An SID is a structure of variable length that uniquely identifies a user or group.


Example: querying a file's security information

In this How To you will learn how to put the information you've learned so far into useful work. Our example displays information of files on a NTFS partition. Note that you must have a NTFS partition, because normal FAT partitions don't support security.

The basics for getting security information is calling the GetFileSecurity API function. Below you can see the whole code used:

    procedure TForm1.ShowSecurityClick(Sender: TObject);
    Var
      SD       : Array[0..200] of Integer; { security descriptor }
      I,J,K,L  : Integer;
      B,B2,B3  : Bool;
      ACL      : PACL;
      ACE      : Pointer;
      SID      : Pointer;
      AM       : Integer; { access mask }
      Name,Dom : Array[0..100] of Char;
    
    begin
      GroupBox1.Caption := 'Security Data for (unknown)';
      B := GetFileSecurity(PChar(Filename.Text),DACL_Security_Information,@SD,SizeOf(SD),I);
      If (Not B) Then Raise Exception.Create('Problem with GFS ('+IntToStr(GetLastError)+').');
      B := GetSecurityDescriptorDACL(@SD,B2,ACL,B3);
      If (Not B) Then Raise Exception.Create('Problem with GSDD ('+IntToStr(GetLastError)+').');
      SecurityData.Clear;
      If (ACL <> nil) Then Begin
        For J := 0 to ACL.ACECount-1 do Begin
          B := GetACE(ACL^,J,ACE);
          If (Not B) Then Raise Exception.Create('Problem with GA ('+IntToStr(GetLastError)+').');
          If (TACEHeader(ACE^).ACEType = Access_Allowed_ACE_Type) Then Begin
            SID := @TAccessAllowedACE(ACE^).SIDStart;
            AM := TAccessAllowedACE(ACE^).Mask;
            B2 := True;
          End
          Else Begin
            SID := @TAccessDeniedACE(ACE^).SIDStart;
            AM := TAccessDeniedACE(ACE^).Mask;
            B2 := False;
          End;
          I := SizeOf(Name); K := SizeOf(Dom);
          B := LookupAccountSID('',SID,Name,I,Dom,K,L);
          If (Not B) Then Raise Exception.Create('Problem with LAS ('+IntToStr(GetLastError)+', '+IntToStr(GetLastError)+').');
          If B2 Then SecurityData.Items.Add(Name+#9+Dom+#9'(A) '+GetAccessRights(AM))
          Else SecurityData.Items.Add(Name+#9+Dom+#9'(D) '+GetAccessRights(AM));
        End;
        SecurityData.Items.Add(IntToStr(ACL.ACECount)+' ACE(s) total.');
      End
      Else SecurityData.Items.Add('No security (FAT partition?)');
      GroupBox1.Caption := 'Security Data for '+FileName.Text;
    end;
    
After we have the security descriptor, we extract the ACL from it using the GetSecurityDescritorDACL function. With the ACL at hand, getting the individual ACEs if just a matter of looping and calling the GetACE function. Then we just retrieve the information we want from the ACE.


ACEs

ACEs store much of interesting information. Firstly, our code checks to see if the ACE allows or denies access. Depending on this information, the ACE is typecast into different types, and a SID and a access mask are extracted. The SID tells to which account the file belongs. This information is retrieved usint eh LookupAccountSID function. Using this function we also get the domain name.

With the Access mask we are able to construct a string presenting what a particular user is allowed (or denied) to do. The R, W, X, D, P, and O rights are for read, write, execute, delete, change permissions, and take ownership respectively.


    The NT Security demo application running.




Conclusion

Now you should know at least the basics of NT's security model. With a security descritor, you can enumerate much of useful information using the above code. It doesn't matter whether you use a file's security descriptor, it might as well be a registry key's secutiry descriptor.

Actually, the most difficult part in programming the security functions of NT is knowing the correct functions. These functions, are, IMHO a bit badly named. Usually I like the naming scheme of the API calls, but naming of the security functions was hard to me initially. However, I've tried to shed some light to the subject, and I hope this How To will help you write more secure applications!