Checking the user-mode app's digital signature for secure communication with the user-mode?

@“Peter_Viscarola_(OSR)” said:

I dont want to lock down the user mode to driver interface, i mostly want to make sure ONLY my own process/executable can communicate with my driver and issue commands to it.

Sorry… how are those two things (“locking down the user-to-driver interface” and “mak[ing] sure ONLY my own process/executable can communicate” with a given driver) different? What am I missing? You’re trying to secure the channel against admin privileged users, so using a Service SID won’t work?

But why should i use Service SID, when i can use the digital signature of the executable and be 100% sure that no other executable can communicate with me? I’m not saying using Service SID is bad, But obviously digital signatures, specially SHA-2, are mathematically proven to be extremely hard (if not Impossible) to forge/bypass. so why shouldn’t everyone use this approach to be 100% sure?

This topic is important, because i think as you know, the amount of drivers out there that are not properly checking the ICOTLs, and therefore making their drivers exploitable, is vast, and driver developers really need to come up with a solid plan that can block any type of unauthorized user mode app from communicating with them, that can be proven to be impossible to bypass (or at least block 99.99999% of attacks)

@brad_H: But Have you seen VMprotect or similar VM based obfuscator being used in any known production driver…?
Well, we use it in production since a couple of years, never had any issues. We protect only a few critical functions to protect our IP from reverse engineering.

@brad_H said:

@“Wilhelm_Nöker” said:
To establish trust in some particular user-mode executable for your driver, Authenticode signatures are not required. You can use public/private key authentication based on the CNG BCrypt* functions (which are available in user-mode and kernel-mode as well):

But isn’t doing the combination of WinVerifyTrust + CertGetCertificateChain from user-mode easier?

Like you said, it’s user-mode, and the result is needed in your driver. But if you’re comfortable with the techniques required to have a driver delegate some task to a user-mode component, then sure, it’s easier that way. I was suggesting a technique that could run completely in kernel-mode.

**Now regarding getting the the path to the executable that send me the IOCTL : **

If i use PsGetCurrentProcess in my IRP_MJ_DEVICE_CONTROL dispatch function, will it always give me the EPROCESS for the process that send the IOCTL? because if so, then i can easily get the process path from there.

I’ve used PsGetCurrentProcess myself in a COM port driver to let an associated diagnostic tool know which process is keeping the COM port open, and it’s certainly good enough for that purpose. But I did most of the remaining work in user-mode (OpenProcess, EnumProcessModules, GetModuleBaseName), so I just don’t know how this would be done in kernel-mode, and whether it’s easy or not. That’s all.

Well, let’s assume that you can actually protect your files and processes - so what more do you need to do? What extra value does this scheme provide?

If a system administrator is determined enough and skillful enough, he will break anything that you attempt to do. The folks at the NSA have proved this point by pre-calculating all of the possible prime factors for common RSA encryption strengths - and that’s what we know that they could do about 10 years ago.

and what does verifying an on disk image versus a signature have with respect to a loaded module that must necessarily have DLL imports and thunks resolved? Even if the on disk signature is valid, any in process or out of process actor can arbitrarily change the behavior of the binary by replacing the code bytes. And those changes can’t be detected by checking the signature of the disk image, and checking the ‘signature’ of the in memory image is impossible, so what’s the plan?

This may thwart the most infantile attacks - but so does a simple ACL. And it will fail utterly under sophisticated attacks - as will a simple ACL. Possibly achieving some nebulous mid level attack protection at the cost of a huge amount of extra complexity?

@brad_H

In this scenario, if i am checking the digital signature of the process that is sending me the IOCTL, you CANNOT bypass is, literally.

This is incorrect. First of all, an admin can install any driver and circumvent any protection you will come up with, and this is pretty easy when there are many third party signed drivers that simply provide an IOCTL interface to read/write memory in kernel mode - you don’t even have to sign your own driver. This allows the attacker to install any driver and “remove” your ObRegisterCallbacks protection for example or do whatever he wants.

Moreover, You will soon realize that the suggested “ObRegisterCallbacks protection” is never a 100% protection since there are legitimate windows services that need to open a handle to your process for all sorts of purposes, so you will have to allow them to open a handle if you don’t want to cause serious system instability - meaning that if someone injects to these processes (such as a critical svchost) he will be able to inject into your “signed” IOCTL caller from there.

What you are doing here is security by obscurity, which probably isn’t worth it (Depending on who you ask and on your specific requirements, different people have different opinions about this subject). You need to understand that if someone tried to write code to invoke an IOCTL in your driver, he will probably also be able to inject code to your “signed” process and run it from there if he wants - it’s not hard.

For example, just to show you these things are pretty easy and well known, look at this project here: https://github.com/notscimmy/libelevate

Just to summarize: If I were you, I would use an ACL to protect the device, and that’s it.

Also, Injection can be done without opening a handle to your process - I guess you have dependencies on DLLs in your “signed app” right? Remember an admin attacker can simply replace any DLL on disk with his own DLL…

@0xrepnz said:
@brad_H

In this scenario, if i am checking the digital signature of the process that is sending me the IOCTL, you CANNOT bypass is, literally.

This is incorrect. First of all, an admin can install any driver and circumvent any protection you will come up with, and this is pretty easy when there are many third party signed drivers that simply provide an IOCTL interface to read/write memory in kernel mode - you don’t even have to sign your own driver. This allows the attacker to install any driver and “remove” your ObRegisterCallbacks protection for example or do whatever he wants.

Although i agree that if a user is already an admin, at the end of the day he can always bypass protections, but you have to realize my goal here is not to secure a system from malicious users, but only to do my job as a driver developer of securing my communications. Just don’t want my driver to end up as one of those poorly written drivers that gets used by rootkits as a way to bypass driver signature enforcement. if i make it extremely difficult for them use my driver for malicious activities, then they will basically switch to another driver.

my goal here is not to secure a system from malicious users, but only to do my job as a driver developer

Then your efforts are being badly misdirected.

Put an ACL on you device object, as has been suggested here multiple times, and you’re done. That’s considered the standard for best practice. Anything beyond that is just window dressing, unless you want to go with a full-on threat analysis and a set of countermeasures focus on your identified threats.

Peter

Remember too that an admin can arbitrarily affect the way that signatures get checked and which ones are acceptable. Exceptions exist, but administrators routinely take advantage of this to trust their internal private certificates in addition to those that the public CAs provide.

An ACL check is no more or less secure than a signature check, but they check different things. A signature check is like a more sophisticated CRC - excellent for detecting corruption or unauthorized modification, but almost irrelevant for security. ACLs don’t even attempt to detect corruption, but they do enforce security across security boundaries. The assumption has to be made that the code enforcing the security boundary has not been compromised. Think about it and you’ll understand why

Just don’t want my driver to end up as one of those poorly written drivers that gets used by rootkits as a way to bypass driver signature enforcement

The main issue about “security” is that it’s almost always a trade-off with something else such as performance, reliability, stability, usability, etc. There is no supported way to perform “WinVerifyTrust” from kernel mode so basically implementing this will require a complex piece of code to develop, maintain and support. I think that in this case, the quality of your driver is much more important than stopping some rootkit developer from abusing your driver. As you said, they will simply move to the next driver they have so what’s the point? I think it’s not worth the potential issues you will encounter in this complex piece of code. Moreover, as we already agreed, this is not really any kind of security boundary you’re trying to protect here, it’s just a trick you want to do in order to “warn off” rootkit developers…

> @brad_H said: > Although i agree that if a user is already an admin, at the end of the day he can always bypass protections, but you have to realize my goal here is not to secure a system from malicious users, but only to do my job as a driver developer of securing my communications. Just don’t want my driver to end up as one of those poorly written drivers that gets used by rootkits as a way to bypass driver signature enforcement. if i make it extremely difficult for them use my driver for malicious activities, then they will basically switch to another driver. As long as you don’t add IOCTLs that allow to read/write all of physical memory like some of these poorly written drivers, then you should be fine.

If you want to know what you should not do, you can check out the drivers in this repo https://github.com/hfiref0x/KDU

@brad_H said:
Well, as i said, in this scenario lets assume that i can protect my files and processes from modification (since I’m already in kernel and we assume the attacker doesn’t have a driver, if he did then its obviously game over)

You can protect files, but you can’t actually protect your running process nearly as well as you think you can.

That said, it appears that nothing anyone here says about that is going to change your mind, so I’m out.

I am really not going to comment on stuff here, because there would be too much to say.

The least I will say @brad_H is that you are very wrong with many things, especially ObCallbacks and virtualization like VMProtect.

To answer your question:

  • You can check for signed binaries in Kernel, Windows does it, always had since VISTA. Look at ci.dll exported function CiCheckSignedFile, it will do everything you want.
  • You cannot “secure” IOCTL’s with this, find another method of communication, like shared mapping. Or even simpler, encrypt your data both ways.

To give you a hand, do note that ci.dll has changed a lot until Windows 10 and there is no documentation for this. You have to manually reverse the structs and function parameters, generate the .lib file and link against your driver.

dumpbin /EXPORTS c:\windows\system32\ci.dll
lib /def:ci.def /machine:x64 /out:ci.lib

Enjoy.

You can check for signed binaries in Kernel, Windows does it

The discussion here is not about whether it’s possible or not, in kernel mode almost everything is possible but is it the right design choice?

Everyone here simply mentioned it’s not supported, and not worth the effort required to maintain this piece of code… Supporting code that uses undocumented structures + functions that often change is not as simple as “making it work in a test environment”. The signature of this function (CiCheckSignedFile) was changed multiple times, so if you use it you have to adjust your code to the OS version and also keep testing your product against insider builds to make sure the function is not changed so you don’t cause BSODs…

This.

@Mecanik said:
I am really not going to comment on stuff here, because there would be too much to say.

The least I will say @brad_H is that you are very wrong with many things, especially ObCallbacks and virtualization like VMProtect.

To answer your question:

  • You can check for signed binaries in Kernel, Windows does it, always had since VISTA. Look at ci.dll exported function CiCheckSignedFile, it will do everything you want.
  • You cannot “secure” IOCTL’s with this, find another method of communication, like shared mapping. Or even simpler, encrypt your data both ways.

To give you a hand, do note that ci.dll has changed a lot until Windows 10 and there is no documentation for this. You have to manually reverse the structs and function parameters, generate the .lib file and link against your driver.

dumpbin /EXPORTS c:\windows\system32\ci.dll
lib /def:ci.def /machine:x64 /out:ci.lib

Enjoy.

Doing this using the ci.dll is a very risky way of doing it considering most of it is undocumented and the structures change vastly from time to time, its basically not a possible solution for a product.

@0xrepnz said:

You can check for signed binaries in Kernel, Windows does it

The discussion here is not about whether it’s possible or not, in kernel mode almost everything is possible but is it the right design choice?

Everyone here simply mentioned it’s not supported, and not worth the effort required to maintain this piece of code… Supporting code that uses undocumented structures + functions that often change is not as simple as “making it work in a test environment”. The signature of this function (CiCheckSignedFile) was changed multiple times, so if you use it you have to adjust your code to the OS version and also keep testing your product against insider builds to make sure the function is not changed so you don’t cause BSODs…

I disagree, by “supported” I`m sure you mean if Microsoft creates documentation and says “we support this”, then it’s “supported”? That’s not how I see it, at all.

There are literally a dozen functions inside NT that haven’t changed (much) since XP, and people still use them. In fact, is it more often better to use “undocumented” functions/functionality for several reasons.

Example: https://social.msdn.microsoft.com/Forums/vstudio/en-US/a084abc6-60c7-476e-92c6-5930856ca70d/win32-getversionex-returns-wrong-informatiion?forum=vcgeneral

Instead of using the “documented” GetVersion(), it is better to use RtlGetVersion() + RtlGetNtVersionNumbers(). No “manifest” needed, no bonanza.

Another example, I use only undocumented functions and system calls in my real life applications; and they are being used by hundreds of users. I simply hate Win32 API, they are slow, can be intercepted easily, etc.

So I really disagree with this “not supported” bonanza, we are 2 decades away from functions listed as “deprecated” on Microsoft website but they are still there, they still work, and that’s that.

As for the ci.dll, the only issue is that Microsoft did not post any documentation about this, hence why people say “not supported”. One developer can simply open ci.dll in IDA and the .pdb is available from Microsoft; making reversing / exporting of these functions quite easy.

Once you created your import .libs, they won’t change. There are many Windows editions released that heavily rely on these functions and Microsoft 100% will not re-invent the wheel and change them.

You can reliably use “undocumented” functions (up to point), but of course you need to understand what you are doing.

@brad_H said:

@Mecanik said:
I am really not going to comment on stuff here, because there would be too much to say.

The least I will say @brad_H is that you are very wrong with many things, especially ObCallbacks and virtualization like VMProtect.

To answer your question:

  • You can check for signed binaries in Kernel, Windows does it, always had since VISTA. Look at ci.dll exported function CiCheckSignedFile, it will do everything you want.
  • You cannot “secure” IOCTL’s with this, find another method of communication, like shared mapping. Or even simpler, encrypt your data both ways.

To give you a hand, do note that ci.dll has changed a lot until Windows 10 and there is no documentation for this. You have to manually reverse the structs and function parameters, generate the .lib file and link against your driver.

dumpbin /EXPORTS c:\windows\system32\ci.dll
lib /def:ci.def /machine:x64 /out:ci.lib

Enjoy.

Doing this using the ci.dll is a very risky way of doing it considering most of it is undocumented and the structures change vastly from time to time, its basically not a possible solution for a product.

Everything is “risky”, even “documented” functionality. That’s not the point, and that doesn’t stop you from using something that’s already there.

In this case, ci.dll has not changed that much besides new functions being added; structures seem the same since Win 7 (tested) tp Win 10 (tested).

Spoken like an experienced student, or a researcher. Who writes software used by hundreds of users. Not to be confused with “hundreds of thousands” or “millions” :slight_smile:

I don’t have time to write a diatribe on this…. But, the point of something being “supported” is that the functionality is documented and the dev owner has agreed they won’t change the contract inherent in the function. This isn’t Linux where we change the parameters of kernel functions between releases. Even more importantly, when a function is “supported” it means you can expect it to properly achieve its documented goal. It acquires the necessary locks. It works at the documented IRQLs. The dependencies of undocumented functions are… undocumented.

This is not to say that you can never use ANY undocumented function. There are, indeed, some functions that have been around “a long time”… have not changed… are well known… and provide utility that’s not otherwise available. It’s a matter of risk, as you recognize. What type of software you write, what that software is used for, the environment in which its used, the consequences of failure, and the ultimate users of the software all impact what level of risk is acceptable. If you write some gritty utility that “hundreds of people” people can download from GitHub… that’s one level of risk. If you write software that’s run on millions of desktops and servers worldwide, that’s a different level of risk. If your software controls some flying contraption, with or without people on board, that’s yet another level of risk.

We all get to choose, evaluate, and determine the level of risk with which we are comfortable. That’s called “engineering.”

Peter

1 Like

@Mecanik:

There are literally a dozen functions inside NT that haven’t changed (much) since XP, and people still use them. In fact, is it more often better to use “undocumented” functions/functionality for several reasons.

I did not say that using any undocumented API is wrong… It’s always a trade-off that you need to think about. If there’s a benefit to using the undocumented API, You have to “evaluate the risk” and decide if it’s worth it, as Peter said. Evaluating the risk can be very hard at times and requires YEARS of experience with windows kernel and knowledge about potential issues and edge cases. We just suggested that in this specific case it’s not worth it, You can read my previous comments to understand why it’s not worth it… In my opinion.

I simply hate Win32 API, they are slow, can be intercepted easily, etc.

1 - the “can be intercepted easily” argument is just plain incorrect. Moreover, even if it would be correct, why do you care?
2 - “hate Win32 API” - well, nobody “loves” it… but is it a reason to take the risk?
3 - “they are slow” - Design decisions that are based on “runtime performance” reasons, should be backed by tests. If you perform a test and
observe a performance overhead that is caused by a Win32 API (And, this issue is solved by switching to a undocumented API) this may be a good
reason to use this undocumented API, after evaluating the risk. Simply rejecting ALL the Win32 API because it’s “slow” is not a good reason in my
opinion - this is called “premature optimization”.

As for the ci.dll, the only issue is that Microsoft did not post any documentation about this, hence why people say “not supported”. One developer can simply open ci.dll in IDA and the .pdb is available from Microsoft; making reversing / exporting of these functions quite easy.

This was already reverse engineered: https://github.com/Ido-Moshe-Github/CiDllDemo
The time spent on reverse engineering these functions is not a consideration… Reverse engineering is something you do once, compare it to supporting millions of customer machines for years and having complicated code in your product.

Once you created your import .libs, they won’t change. There are many Windows editions released that heavily rely on these functions and Microsoft 100% will not re-invent the wheel and change them.

Why are you so sure about it? MSFT already changed this specific function in the past, so how can you be sure?

Also, it’s not only whether “it’s going to change or not” - there are caveats to using undocumented APIs… The simplest example is using APC - How are you handling the unload of your driver safely? Are you aware of the synchronization and locking issues?

1 Like