OSRLogoOSRLogoOSRLogo x Seminar Ad

Everything Windows Driver Development

GoToHomePage xLoginx

    Thu, 14 Mar 2019     118020 members


  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Keeping Secrets - Windows Security (Part III)

In the last installment of our continuing series on Security (Volume 6, Number 5, September-October 1999), we discussed the basics of the security Identifier (SID) and how to extract it for use in constructing an ACL.  In this article we will move forward and describe how to actually create a security descriptor.


Building an Access Control List (ACL)

An access control list is nothing more than a series of individual access control entries (ACEs.)  Each ACE describes an SID, an access mask, and some control information that indicates how to interpret the ACE (this was covered in greater detail in the first part of this series published in Volume 6, Number 3, May-June 1999).

Thus, constructing an ACL consists of several key steps:


·    Allocating storage for an ACL;

·    Initializing the ACL storage;

·    Adding zero (or more) ACEs to the ACL


Let’s take each of these steps one at a time.  First we allocate storage (see Figure 1).  In general, security information is only accessed at PASSIVE_LEVEL, so we can allow ourselves to use pageable memory.


dacl = ExAllocatePool(PagedPool, PAGE_SIZE);

    if (!dacl) {



    status = RtlCreateAcl(dacl, PAGE_SIZE, ACL_REVISION);

    if (!NT_SUCCESS(status)) {





Figure 1 — Allocating Storage for an ACL


This code fragment simply establishes an empty ACL – and it allocates a significant chunk of memory, since we do not know the size required for the ACL.


Note that an ACL, such as this one that is empty, would indicate that access should be denied to anyone who attempts to access the object.  Thus, the next step is to add ACE entries to this ACL (see Figure 2).  Note that we continue to utilize the same memory region.


status = RtlAddAccessAllowedAce(dacl, ACL_REVISION, FILE_ALL_ACCESS, SeExports->SeWorldSid);

    if (!NT_SUCCESS(status)) {





Figure 2 — Adding ACEs to the ACL


This ACE would constitute the generic “allow everyone” access.  Of course, the ACL could have been more specific by providing some other SID, or some other access rights.  If we had wanted to add a different type of ACE to the ACL, we would have to construct it directly because neither the IFS Kit, nor the DDK export any other runtime library routines for constructing other types of ACEs.  Precisely how this is done is outside the scope of this article.


Building a Security Descriptor

Now that we have an ACL, we can move on to the next step of actually constructing a security descriptor by using this ACL.  The steps for building a security descriptor are similarly mundane:


·    Allocate storage for the Security Descriptor;

·    Initialize the Security Descriptor;

·    Instantiate the Owner of the Security Descriptor

·    Instantiate the DACL of the Security Descriptor


There are other possible steps (setting the group and SACL of the Security Descriptor) but they are similar to the steps we are describing here and are thus outside the scope of this article.


Allocating and initializing a security descriptor is shown in Figure 3 and setting the owner field is shown in Figure 4.


sd = ExAllocatePool(PagedPool, PAGE_SIZE);

    if (!sd) {



    RtlZeroMemory(sd, PAGE_SIZE);

    status = RtlCreateSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION);

    if (!NT_SUCCESS(status)) {


        return status;



Figure 3 — Allocating & Initializing a Security Descriptor


status = RtlSetOwnerSecurityDescriptor(sd, localSid, FALSE);

    if (!NT_SUCCESS(status)) {


        return status;



Figure 4 — Setting Owner Field


In this case, we note that the owner of the Security Descriptor is the “local system” SID that we constructed in Part II of this series.  Thus, this security descriptor would identify this file as belonging to the local computer system.  Of course, the owner field is important because the owner of an object always has the right to set the DACL for the object – even if the DACL does not provide that right directly!


We can now add the ACL that we constructed earlier to this security descriptor as shown in Figure 5.


status = RtlSetDaclSecurityDescriptor(sd, TRUE, dacl, FALSE);

    if (!NT_SUCCESS(status)) {



        return status;



Figure 5 — Adding ACL to Security Descriptor


One optional step that we use to make sure we have done everything right is to validate the security descriptor.  This is shown in Figure 6.


if (!RtlValidSecurityDescriptor(sd)) {




Figure 6 — Optional Validation for Security Descriptor


Thus, if the security descriptor is constructed incorrectly, we would note it at this point.


Of course, now that we have constructed a security descriptor, we can use it to apply to existing objects.  This would include file objects (assuming that the underlying file system supports security), registry keys, device objects, etc.


The simplest way to accomplish this is to use ZwSetSecurityObject, which is described in the Windows XP IFS Kit.  Another way to do this with a file system is to construct an IRP_MJ_SET_SECURITY, although this only works with file systems and hence cannot be used for other types of objects.


In addition to the normal security mechanism, where a security descriptor is used to protect an object, NT defines a separate administrative mechanism that allows certain users to perform specific operations, even if those operations are inconsistent with the access rights of the object itself.


For example, a user that has backup privilege can open a file for read access, even if such access is not allowed via the access controls in place on the file.  There are many such privileges and many of them relate to the security mechanism itself.  For example SE_CREATE_TOKEN_PRIVILEGE is the right of the caller to create a security token.  Without this privilege, attempts by the caller to create a security token will be rebuffed.


Several other privileges that could be of interest to kernel mode developers include:


·    SE_LOCK_MEMORY_PRIVILEGE – this gives users the right to lock regions of their memory in place.  For example, user applications that wish to lock their virtual address space must have this privilege.

·    SE_INCREASE_TOKEN_PRIVILEGE – this gives users the right to increase their memory quotas.

·    SE_LOAD_DRIVER_PRIVILEGE – this gives the caller the right to load a driver.  Failure to have this privilege will cause any attempt to load a driver to fail.

·    SE_BACKUP_PRIVILEGE – this gives the caller the right to open (for read access) any object in the system.

·   SE_RESTORE_PRIVILEGE – this gives the caller the right to open (for write access) any object in the system.

·    SE_SHUTDOWN_PRIVILEGE – this gives the caller the right to shut down the system.

·    SE_MANAGE_VOLUME_PRIVILEGE – this gives the caller the right to perform certain volume-level operations.


Whether or not a particular user possesses a specific privilege is defined by the policy of the given system; when a user authenticates and their security context is first constructed, a list of all their available privileges is computed and stored.  However, merely “having” a privilege is not sufficient to perform the operation.  In addition, an application that wishes to take advantage of a privilege must also enable the privilege.


The simplest way to achieve this is using the ZwAdjustPrivilegesToken API that is part of the Windows XP IFS Kit.  This function’s prototype is shown in Figure 7.





ZwAdjustPrivilegesToken (

    IN HANDLE TokenHandle,

    IN BOOLEAN DisableAllPrivileges,


    IN ULONG BufferLength OPTIONAL,


    OUT PULONG ReturnLength



Figure 7 — ZwAdjustPrivilegesToken


The parameters here indicate:


·    TokenHandle – this is the token being adjusted.  Typically, this will be a handle to the local process token.

·    DisableAllPrivileges – this indicates if all existing privileges should be disabled (reset).  This would be the case when a new security policy was being activated for this token.

·    NewState – this indicates the new privilege state being established.  Typically, this would indicate the new privilege that is being enabled.

·    BufferLength – this indicates the size of the output parameter PreviousState.

·    PreviousState – this indicates the previous privilege state, prior to adjustment.  It is often used to subsequently reset privileges after an operation is performed.

·    ReturnLength – this indicates the number of bytes used in the PreviousState buffer; if the buffer is too small, this value will indicate how big it must be for the operation to be successful.


There are a number of ways this function can be utilized.  For example, the existing token state can be retrieved simply by passing in a NULL pointer for the NewState parameter (which is optional).


Another way to use this is to enable a privilege, perform the operation (e.g., open a file for backup) and then restore the previous privilege state.  This allows the privileged operation to proceed, and yet also ensures that a subsequent return from the kernel mode driver does not inadvertently leave the privilege enabled.


In our own drivers we sometimes wish to enable or disable a single privilege.  We use the routine shown in Figure 8 to achieve this.


static NTSTATUS AdjustPrivilege(ULONG Privilege, BOOLEAN Enable)


    NTSTATUS status;


    HANDLE tokenHandle;




    // Open current process token

    status = ZwOpenProcessToken(NtCurrentProcess(),



    if (!NT_SUCCESS(status)) {

        DbgPrint("NtOpenProcessToken failed, status 0x%x\n", status);

        return status;



    // Set up the information about the privilege we are adjusting

    privSet.PrivilegeCount = 1;

    privSet.Privileges[0].Luid = RtlConvertUlongToLuid(Privilege);

    if (Enable) {

        privSet.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    } else {

        privSet.Privileges[0].Attributes = 0;


    status = ZwAdjustPrivilegesToken(tokenHandle,

                                     FALSE, // don't disable all privileges



                                     NULL, // old privileges - don't care

                                     NULL); // returned length

    if (!NT_SUCCESS(status)) {

        DbgPrint("ZwAdjustPrivilegesToken failed, status 0x%x\n", status);



    // Close the process token handle

    (void) ZwClose(tokenHandle);


    return status;


Figure 8 — Enable/Disable Single Privilege


This can be a very powerful technique for controlling access rights, for instance.  By using backup and restore privilege (assuming they are available) the normal security mechanism is bypassed.




While there are numerous other avenues for exploring security (the details of access control entries, for instance), most of those are not of general interest to kernel mode software developers.  However, understanding the rudiments of security can prove to be useful for some kernel mode software development.  In future articles we will focus on specific aspects of security, rather than on the general security model in NT.




Related Articles
Keeping Secrets - Windows NT Security (Part II)
Keeping Secrets - Windows NT Security (Part I)
You've Gotta Use Protection -- Inside Driver & Device Security
Still Feeling Insecure? - IoCreateDeviceSecure( ) for Windows 2K/XP/.NET
Securing Device Interfaces - A Better Approach than Sending an SD
Security During Create Operations
Locking Down Drivers - A Survey of Techniques
What is Coming with Vista - Limited User Access

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

Post Your Comments.
Print this article.
Email this article.
bottom nav links