OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Sat, 21 Oct 2017     115112 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
Downloads
ListServer / Forum
Driver Jobs
  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) {

        return;

    }

    status = RtlCreateAcl(dacl, PAGE_SIZE, ACL_REVISION);

    if (!NT_SUCCESS(status)) {

        ExFreePool(dacl);

        return;

    }

 

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)) {

        ExFreePool(dacl);

        return;

    }

 

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) {

        return STATUS_INSUFFICIENT_RESOURCES;

    }

    RtlZeroMemory(sd, PAGE_SIZE);

    status = RtlCreateSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION);

    if (!NT_SUCCESS(status)) {

        ExFreePool(sd);

        return status;

    }

 

Figure 3 — Allocating & Initializing a Security Descriptor

 

status = RtlSetOwnerSecurityDescriptor(sd, localSid, FALSE);

    if (!NT_SUCCESS(status)) {

        ExFreePool(sd);

        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)) {

        ExFreePool(dacl);

        ExFreePool(sd);

        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)) {

        DbgBreakPoint();

    }

 

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.

Privileges

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.

 

NTSYSCALLAPI

NTSTATUS

NTAPI

ZwAdjustPrivilegesToken (

    IN HANDLE TokenHandle,

    IN BOOLEAN DisableAllPrivileges,

    IN PTOKEN_PRIVILEGES NewState OPTIONAL,

    IN ULONG BufferLength OPTIONAL,

    OUT PTOKEN_PRIVILEGES PreviousState 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;

    TOKEN_PRIVILEGES privSet;

    HANDLE tokenHandle;

    TOKEN_PRIVILEGES tokenPriv;

 

 

    // Open current process token

    status = ZwOpenProcessToken(NtCurrentProcess(),

                                TOKEN_ALL_ACCESS,

                                &tokenHandle);

    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

                                     &privSet,

                                     sizeof(privSet),

                                     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.

 

Conclusions

 

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

Writing WDF Drivers I: Core Concepts
LAB

Nashua (Amherst), NH
15-19 May 2017

Writing WDF Drivers II: Advanced Implementation Techniques
LAB

Nashua (Amherst), NH
23-26 May 2017

Kernel Debugging and Crash Analysis
LAB

Dulles (Sterling), VA
26-30 Jun 2017

Windows Internals and Software Driver Development
LAB

Nashua (Amherst), NH
24-28 Jul 2017

 
 
 
 
x
LetUsHelp
 

Need to develop a Windows file system solution?

We've got a kit for that.

Need Windows internals or kernel driver expertise?

Bring us your most challenging project - we can help!

System hangs/crashes?

We've got a special diagnostic team that's standing by.

Visit the OSR Corporate Web site for more information about how OSR can help!

 
bottom nav links