Recently, we've seen a series of questions concerning the retrieval of executable image names. Presumably, these developers believe that by comparing the name of the executable image with the name of their service, they can provide some additional level of security. Unfortunately, doing so not only relies upon undocumented techniques, but also provides no real additional security. We spent some time talking about this at OSR and decided to provide a quick survey of the techniques that are available for accomplishing this.
What to Consider
In deciding how to provide secure communications between your service and driver, there are several points to consider:
- The goals of securing this communications channel
- The operations to be secured - note that it is possible to allow some channels while disallowing others
- The environment in which the driver operating
- The type of driver being secured
The Image Name
First, let us note that using the name of the executable image is not secure. Indeed, with the increasing accumulation of service threads within svchost.exe, it is quite common for the name to be "svchost.exe". This name can be spoofed by even the most rudimentary of Trojan horse programs simply by using the same name. Note that this is not a theoretical attack vector as it has been used in recent computer attacks.
Admin Access Only
In considering the design of your communications, it is important to clearly articulate the goals of securing the service-to-driver communications channel. For example, when we first started discussing this issue, one perspective someone had was, "Just lock it down so only Admin Group people can access it". In discussing this, we actually talked about reasons why, while simple, this might not be the best security policy:
- It grants rights to your service far beyond what it needs to "get the job done"
- In modern PCs, many users log in using accounts that are members of the admin group
- Doing so does not grant you network (or domain) level permissions that are often useful or necessary
Use a Separate Account
How about the use of a separate, dedicated account for the service? Doing so, allows the developer to determine exactly the set of rights required - establishing such credentials during installation. An interesting side-effect of this is that it can circumvent "generic" attacks from an account with Admin group privileges (of course, a specific attack can be constructed by changing the shared password between the service and driver). This is because by using a specific account, you can remove Admin group access. This can be overcome, but requires an additional, specific step on the part of a would-be attacker.
To use a separate account, the installation process would create a new account (either on the local machine or within the domain) and then use the resulting account information (notably the SID) to construct the specific security. Perhaps the simplest way to achieve this is by using the SDDL semantics that are described for the use in .INF files. The "trick" here is to use the explicit SID of the account rather than one of the built-in, well-known SIDs. This becomes a registry-based characteristic of the device for the driver, and will be automatically applied by the Plug and Play Manager. This technique is only available to drivers that are installed or started by the Plug and Play Manager.
Thus, for plug and play devices, this is likely to be the best and simplest solution. Note that another avenue explored was the use of IoCreateDeviceSecure. Unfortunately, this routine does not support arbitrary SID values in the SDDL string that is provided as the security descriptor argument.
Use the Registry
A particularly clever solution that was suggested is to implement your own security by storing the (binary) security descriptor in the registry. You can retrieve that security descriptor (in REG_BINARY format) during system initialization and then use it directly to perform an access check (RtlIsValidSecurityDescriptor) when your device is opened.
In this case, a driver can use a (validated) security descriptor in IRP_MJ_CREATE simply by calling SeAccessCheck, passing in the security descriptor from the registry and the necessary parameters from the create parameters themselves. The advantage of this approach is it is freely available for any driver and is easy to implement with a few dozen lines of code.
Check Against Multiple SDs
A more sophisticated implementation of the security check might be to perform that check against multiple security descriptors. Your driver might have query interfaces that protect those operations at one level (relatively low), while other interfaces represent state changes, protected at a different (higher) level. Still other interfaces represent irreversible operations ("initiate the self-destruct sequence on the device") that must be protected at the highest levels. Thus, a driver might extract three different security descriptors from the registry. A driver could compute the allowed access in its IRP_MJ_CREATE dispatch entry point (certainly the easiest solution) or it might capture the necessary parameters and compute the access when the operation is performed.
What About File System Software?
Drivers in the file system space (linking against ntifs.h) can theoretically directly change the security descriptor on their device object. We wrote code to do this, using documented routines, but ultimately concluded that it was far too much effort, particularly given the power of simply performing the access checks directly - a far simpler, equally workable approach.
Another technique worthy of consideration is to use privileges to protect the driver. The call to check privileges is in the DDK header files. Unfortunately, the actual values of the various privileges are not, and are only available for those drivers in the IFS Kit environment. Thus, this technique has more limited usability. For file system drivers, it might be useful to check specific privileges (e.g., SeManageVolume Privilege). This is done using the SeSinglePrivilegeCheck API. Again, it is important that this type of check be done using the correct set of credentials (generally in IRP_MJ_CREATE).
It is possible to augment the security of the system in other ways as well. One of the most interesting we have observed resulted in the driver dynamically generating an AES key that was then directly written into a memory location of the service. The service and driver then used that key to encrypt all subsequent operations between them. Thus, even a filter driver inserted between the two of them could not directly interfere in their specific operations - this was clearly an environment in which any tampering with the information was to be prevented. For most drivers (and services) this level of protection is likely to be far more than the circumstances require.
Security is one of those aspects of development that seems (on the surface) to be straight-forward and yet as you learn more you find out how easy it is to do it wrong - and the cost of doing it wrong today is compromised systems, lost information, and denial of service attacks. See Sidebar Other Security Considerations
With that said, we know there are far better methods available to secure your control device object for your service's use than by checking the service executable name - use them!
Other Security Considerations
Never trust anything from an unauthenticated caller. If it is your service, track it. If your service terminates, ensure you can handle that state so that a subsequent call is not granted appropriate access.
Don't be afraid to use exclusive access on your control device. This simplifies tracking who (or what) is calling your driver.
Do not rely upon statically coded information. This is too easily compromised. Do not embed passwords in your software. Trust that the secure pieces of the OS are secure.
There is no "security through obscurity". The advantage of obscuring information is to make it more difficult to compromise. This does not protect against compromise and is not sufficient by itself.
There are no secure systems. Ultimately the biggest enemy to security is the user. Try to restrict what users can (and do) perform in normal activities. Use the event log for anything strange - like attempts to access your device that you deny.
Always assume your driver (and service) are operating in a hostile environment. Assume that everything outside the trusted computing base is being written specifically to compromise your driver (and service).
Don't worry about the trusted computing base being compromised - if it is, there is nothing your driver can do to resolve that fundamental underlying issue.
When considering if something is done "securely," never trust your own judgment. Ensure that you get multiple opinions.
- Consider your security implementation from the "adversary" position - how would you compromise your own system?