Geoff Chappell - Software Analyst
WORK IN PROGRESS
Recent releases of Windows 10 can be configured to allow self-signed kernel-mode drivers in ordinary use with Secure Boot enabled. Security is assured by a demonstration of ownership: a System Integrity policy file must be signed with the Secure Boot Platform Key. The feature is active only in specially licensed editions, except for a contrived case that this article demonstrates.
Imagine you have bought a computer and installed Windows on to it. You figure that computers have the general purpose of running whatever programs you want and you set about writing a program of your own. After debugging and testing, you’re satisfied that your code meets all the technical specifications for a Windows executable in general and for the particular Windows API functions it depends on. You’re ready to start using this program for real, but before you can throw away such crutches as Developer Mode, you must send your program to Microsoft to get a Microsoft signature that Windows on your computer will recognise as Microsoft’s permission for your program to run on your computer in ordinary use. Wait, you ask, what’s this about needing a Microsoft signature for my own program on my own computer? When did permission for this become Microsoft’s to give? How is running my own software on my own computer anyone’s business but my own?
Hold that thought for now. I will get back to how what you do just with your computer can be the reasonable interest of others and especially of Microsoft. First, though, I had better allay any slight panic you may have picked up. For Windows on computers, if not when locked to tablets and phones, what I just described for user-mode software is for now, for the most part, just a dystopian future that may never happen.
For kernel-mode software, by contrast, this future has been the present for a while. As far back as 64-bit Windows Vista, you could run your own driver on your own computers, which I mean as generalising to all the computers in your possibly very large enterprise, without getting it signed by Microsoft, but you certainly couldn’t just sign it yourself with a certificate of your own making and have Windows load it for ordinary use. Unless you were content to run Windows under a debugger or in some insecure configuration such as Test Mode, you couldn’t self-sign with a certificate that you alone created and you had to involve Microsoft at least indirectly. To ready your driver for ordinary use without sending it to Microsoft, you had to sign it with a so-called Software Publisher Certificate (SPC) from a third-party certification authority (CA) in combination with a publicly available cross-certificate that Microsoft issued to the CA so that Windows on all computers could know the CA had Microsoft’s permission.
Less far back, Microsoft announced in April 2015 that this relatively easy cross-signing of drivers was to stop. Among the Driver Signing changes in Windows 10 would be that “all new Windows 10 kernel mode drivers must be submitted to and digitally signed by the Windows Hardware Developer Center Dashboard portal.” Perhaps out of caution that its readers might not immediately register the change’s impact, it continued with a re-expression: “Windows 10 will not load new kernel mode drivers which are not signed by the portal.” After Windows 10 was formally released in July 2015, something seems to have gone wrong somehow, such that Microsoft silently relaxed the enforcement of the new requirements for the 1511 release later that year. How much the inevitable confusion was settled by Microsoft’s assertion in a new announcement, of Driver Signing Changes in Windows 10, version 1607, that the original changes “remained only a policy statement” is doubtful, but enforcement was eventually restored and is plainly here to stay. If you write a kernel-mode driver and you want it to load on Windows 10 in its 1607 release or later in ordinary configurations and especially if Secure Boot is enabled, then (unless you are able and willing to exploit some Back Doors For Cross-Signed Drivers) you must send your driver to Microsoft to get a Microsoft signature—yes, even to load just on your own computers.
Now, please don’t take me as objecting to the very idea of needing Microsoft’s permission. If you write a kernel-mode driver to run on other people’s computers—and by far the greatest number are written for such distribution—then I think it’s not the slightest bit unreasonable if Windows on those other computers refuses to run your driver unless it comes with a badge of approval from some authority whose recognition by Windows is secure from being tampered with. A natural such authority is Microsoft and the natural recognition is to verify digital signatures on drivers against public keys that are, for instance, hard-coded into Windows or, even better, secured by a chain of trust that reaches back to the firmware.
Such protection is important because kernel-mode drivers are extremely powerful. In kernel mode, you are not just at another level, higher even than running on the SYSTEM account. It’s more like you are beyond levels, because your execution is not constrained at all by privileges or permissions. It’s not just that in kernel mode you can see pretty much everything. You can change an awful lot too, including much of what user-mode software sees of the system. Such power is inevitably attractive, even irresistably so, and sadly not just to good programmers. The history of Windows is littered not just with kernel-mode malware but with commercially distributed kernel-mode code that is defective beyond any reasonable allowance for defects in hand-crafted work. Indeed, some—by anti-malware vendors, no less—might better be regarded as abusive.
Thus have I got you back to that thought that I asked you to hold. It’s all very well if you say that all you want is for your kernel-mode driver to execute only on your own computers, but how do I, Microsoft and the rest of the world know it will stay only on your computers? You could be a good person, with good and honest intent, and even be a good programmer. But it could also be that you are up to no good and your driver is malware. Perhaps you exploit some security vulnerability to gain administrative access to our computers and you install your driver without our even noticing (until the damage is done). Perhaps you induce us to run some program as an administrator, e.g., by persuading us that your software is a security tool for our benefit, and your program then installs your driver even though we didn’t mean for you to do any such thing. Either way, we naturally look to Microsoft to protect us from your being able to force your potentially unsafe or even mischievous driver onto our computers.
But this reasonable interest that I’ve just argued Microsoft has in what you do just with your own computers isn’t obviously what Microsoft has acted from. If you’re genuine about running your driver only on your own computers, then from your perspective Microsoft’s particular method of protection is absurd overkill: you must get Microsoft’s signature so that other Windows users can know that their loading of your driver has Microsoft’s blessing even though you don’t want that they can load it at all. From our perspective, too, what Microsoft has done is a good bit less than ideal. Even if you felt pressed enough to have jumped through the hoops and got Microsoft’s signature, we would all be better served for our Windows security if Microsoft hadn’t pushed you to it but had instead left your driver as some untrusted thing that Windows on our computers simply won’t load. Why has Microsoft not arranged for your private use and our better protection?
One reason might be that the technology doesn’t exist, or would be too much trouble or too costly to implement, for having Windows on your computer know that your driver is indigenous and can be allowed to load but Windows on our computers can know that your driver is foreign and must be refused. If this were so, then I for one would be firmly with Microsoft for requiring ever stronger certification that drivers are fit for distribution. I could, of course, find other points to complain about driver-signing, notably that Microsoft greatly increased the work of getting drivers into distribution but rather than trouble much to get a framework ready for certifying drivers as software, Microsoft just warmed over something that isn’t even half-way evolved from testing hardware for compatibility. But such grumbles can all be put aside because technology for recognising that a driver is running on its owner’s computer obviously does exist and can’t credibly be dismissed as too much trouble—well, not by Microsoft.
The technology’s called Secure Boot. Microsoft contributed substantially to its development and Microsoft’s marketing power was instrumental to its wide deployment. For a computer to get a Windows 10 logo—and how many computer manufacturers could stay in business without that?—it must not only implement Secure Boot in its firmware but have it set up with the Windows boot manager pre-approved for execution and with Microsoft pre-approved for subsequent reconfiguration of what else the firmware will and won’t execute.
At the heart of what makes Secure Boot secure is the Platform Key. This is a public key, typically in a certificate, that is enrolled with the firmware and thereafter acts as a root of trust. It can be read freely but can be written only from a firmware menu or if authenticated by a demonstrated ability to sign with it. Unless you leave the matching private key lying around for malware to abuse—the technological equivalent of leaving a key in a lock—the Platform Key is for all practical purposes a secure indication of your ownership of the computer. Signing anything with the Platform Key, including to issue certificates and to sign with those, is an expression of ownership that you might think should trump all other expressions of authority over your own computers. In the simplest possible example, you might sign a kernel-mode driver with your Platform Key and not unreasonably think that Windows would then have the means of allowing your driver on your computers and denying it on everyone else’s. If only Windows would notice!
Of course you know now that Windows does notice, else this article would not exist. That Windows 10 requires Microsoft signatures on your own kernel-mode drivers just to load them only on your own computers is one way to protect other people’s computers but it certainly isn’t required just for this. Not only does Secure Boot provide Windows with the means in theory to distinguish that your driver is being loaded on your own computers rather than on someone else’s, but Windows 10 puts theory to practice—and not just to the simplest possible practice but with elaboration. All releases of Windows 10 recognise when one sort or another of policy file is signed with the Platform Key and thus expresses the wishes of the computer’s owner, and among the expressions that Windows can recognise in these policy files is to specify non-Microsoft certificates for Windows to allow in signatures on kernel-mode drivers—yes, even certificates that you make yourself.
Since this runs so much against Microsoft’s own public descriptions of Microsoft’s driver-signing requirements for Windows 10, let’s have it again but with emphasis. All releases of Windows 10 have code for allowing your self-signed drivers to load on your computers in real-world configurations while protecting others from your drivers and you from theirs. But for the implementation in recent releases of Windows 10, Microsoft doesn’t license you to use this code.
To say it this way is perhaps to put words in Microsoft’s mouth, especially if you interpret me as saying why Microsoft has done it. Please be sure to understand that although I do mean to say what Microsoft has done and how Microsoft has done it, I can’t know why Microsoft has done it. I can’t even know if Microsoft’s own staff know why Microsoft has done it. I say that custom kernel-mode driver signing is a licensing issue because that’s how Microsoft’s programmers evidently thought of it when they wrote the relevant code. Whether the kernel-mode Code Integrity module (CI.DLL) allows a driver to have a custom root certificate in its signature instead of being required to have one or another well-known Microsoft root is retrieved from the registry by calling a function named ZwQueryLicenseValue from an internal procedure which Microsoft’s published symbol files name CipCheckLicensing. The boot loader (WINLOAD.EFI) decides its use of the feature a little differently. It reads from a registry value named Licensed. Its routine for making the assessment is named OslpCheckLicensedCodeIntegrityOptions. If this is not about licensing, then some manager failed badly at getting the message through to the programmers.
But is it wholly a question of licensing? The incontestable answer would come from naming an edition that is suitably licensed and giving directions for configuring it so that it loads a self-signed driver. But I don’t know for certain even of one edition that allows this. I can tell you that what will distinguish it is that the license value CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners is non-zero. Searching the Internet for this gets a few matches, including two reports that this license value is 1 in something named EnterpriseG. Microsoft added PRODUCT_ENTERPRISEG to its published header files for Windows programming in advance of the 1703 release. Plausibly it’s the subject of Microsoft’s Announcing Windows 10 China Government Edition and the new Surface Pro on 23rd May 2017. But who’s really to know? I have not yet found how to get a copy for inspection, certainly not just from a Visual Studio subscription, but perhaps I just have to look harder or smarter. There is, after all, some incentive: from what little Microsoft has described of this edition’s features, it sounds like the sort of Windows that many ordinary Windows users the world over would prefer, if only for being free of telemetry and other irritants. But if it has this particular feature of being configurable to load self-signed drivers under the protection of Secure Boot, I don’t expect Microsoft to say. Indeed, I know of nowhere that Microsoft says even obliquely that any of the numerous differently licensed editions of Windows 10 will load drivers that do not have at least a cross-signature except by being run in some Test Mode or under a debugger.
From what Microsoft presents publicly as having coded into Windows, there looks to be a split purpose regarding Secure Boot. Microsoft is pleased to present Secure Boot and its Platform Key, etc., as a valuable new development to help Secure the Windows 10 boot process by assuring that the first few parts of Windows that the firmware loads truly do start Windows as Microsoft intends. That surely is valuable, yet what Microsoft itself describes as the “chain of trust established by Secure Boot” is apparently not sound enough to continue into Windows for deciding what Windows drivers to trust.
Could it be that from Microsoft’s perspective, there are bigger issues in play—and a bigger purpose for Secure Boot—than the protection of Windows users from mischievous code? If so, it’s well past time to bring these issues to the front and centre for clear assessment instead of smuggling them in with benefits for security that may turn out to be relatively incidental. Indeed, from what I see of how Windows uses Secure Boot, and especially Secure Boot policy files and relevant firmware variables, I have the impression that it’s much less about protecting Windows from bootkits than locking Windows to devices. Perhaps the bigger issue behind why Microsoft does not make a public feature of accepting Secure Boot’s root of trust for validating Windows drivers is the very one that Secure Boot is founded on: ownership, just not in the same sense that most of us presently think of it.
What does it mean to own a computer? What does it mean to own anything now that ever more machinery from cars to tractors to jet engines, and even consumer goods like refrigerators and toasters, are small computers? There appears to be a growing social question surrounding repairing and tinkering, e.g., A “right to repair” movement tools up - If it’s broken, you can’t fix it. The problem is not just that increasing complexity takes such work beyond the competence of skilled amateurs. That’s arguably just natural and even inevitable. It’s not even that manufacturers sometimes withold technical specifications that might help with the complexity. What’s extra, and perhaps new, now that machinery and appliances are specialised computers, are the assertions of intellectual-property rights to the software, not just to protect the software from being copied but to exert various forms of continuing control over both the software and the physical product.
For Windows specifically, I take it as understandable that Microsoft locks it down in all sorts of ways for phones and tablets as integrated products that are both hardware and software. If Microsoft’s move into making and selling “devices” comes with a closed ecosystem in which Microsoft controls what software Windows runs on those devices, then it looks to me like there’s plenty of precedent for society’s acceptance of this control for such devices in large competitive markets over the decades in which we’ve come to depend on mobile phones and we’ve let “jailbreak” become a verb. Whether such control is appropriate for Windows as a general-purpose operating system for general-purpose computers—indeed, as the overwhelmingly dominant operating system for such computers—is another matter.
Whatever may be brewing for such questions over the coming decade, let’s just plant a flag for what Microsoft made of it in 2017. Though some talk of a “new Microsoft” that has learnt from its anti-trust experience and has opened to Linux and embraced open source, on this question of whether Windows is a general-purpose platform for your use of your own software on your own computers, this new Microsoft is not open and is not your friend.
It’s not as if Microsoft didn’t solve the security implications that your running your own drivers on your own computers can have for the wider Windows ecosystem. My denunciation isn’t even that Microsoft built the solution into Windows but kept it secret. It is instead that Microsoft made Windows capable of letting you, the owner of your computer, run whatever drivers you want, but only if Microsoft licenses you to.
This is not licensing to create price points around Windows running your software more or less well, e.g., by using more processors or limiting how much memory Windows uses. It’s not about Windows running more or less of your software, as when low-budget editions permit only a few applications to be open at any one time, It’s not about whether software that Microsoft supplies with Windows will run at all, as when earlier versions of Windows that had built-in games would run some of them only if you had paid for a superior edition. Those are all ways that a manufacturer or supplier charges differently according to some rough assessment of how much use you make of the product or service and of how much it means to you. Such price differentiation is not always welcome to consumers but it is well established, if too often not well explained. What’s new here is the presumption that for whether Windows will run your own software on your own computer, the owner of the decision is not you but Microsoft. It’s not a technical limitation. It’s not for security. It’s a change in the nature of Windows away from its being a general-purpose operating system for general-purpose computers.
After so many words to allege that something’s new in Windows but has escaped the attention of apparently everyone but me, you should want to see very soon that I can back up what I say and not much less soon that you can reproduce my evidence for your closer examination. Fortunately for my keeping your attention in this industry that likes to see a demonstrated proof of concept above all else, it turns out that I can do better than ask you to trust a wordy description of complicated Windows code that you can’t execute unless you have the license and can’t meaningfully inspect unless you’re at least a competent reverse engineer. Because the equivalent of CI’s licensing check is not solidly enforced in the earlier, simpler execution conditions of WINLOAD, it can be side-stepped for drivers that are loaded by WINLOAD rather than by the kernel. We can then glimpse experimentally what we could otherwise only deduce theoretically. The following snapshot shows a little exploration of a Windows 10 Professional installation on which I’ve loaded a small driver named selfsign.sys. Click on the picture if you’re interested enough to want it expanded to full size and high definition:
The Command Prompt in the foreground shows first that we really are in Windows 10, specifically in its 1709 release. Next the sc query command confirms that the Service Control Manager knows that a service named SelfSign is in fact a kernel driver and that it’s running. Finally, a selfsign command, written as a companion to the driver, confirms that the driver is not just running but is functional (albeit for what little is expected of it).
The administrative Command Prompt on the right runs the PowerShell Confirm-SecureBootUEFI command to see that Secure Boot is enabled. This would be completely unremarkable if the driver has a Microsoft signature. It would be only slightly less unremarkable if the driver is cross-signed and has somehow sneaked through some semi-documented compatibility provision for upgraded systems. To confirm that neither case applies, the sc qc command has been run to show where the driver exists as a file and then the background shows the Properties for that file as found in the File Explorer.
The Digital Signatures tab of the “selfsign.sys Properties” shows that the driver has two signatures. That it has two isn’t relevant to the problem. It’s just what’s needed these days for a driver to load on multiple Windows versions. A primary signature uses SHA-1 for hashing so that the driver can load on older Windows versions that don’t recognise any stronger hash. A nested signature uses SHA-256 for newer Windows versions. The remaining dialogs in the background show the Digital Signature Details for this nested signature and then its Certification Path. See that all the certificates are My Own this and My Own that, because that’s how I named them when I made them myself (well, with Microsoft’s makecert tool).
The picture is not a mock-up. Stick with me and you will be able to produce it yourself. I haven't patched any Windows code as my way of getting Windows to do something it otherwise never would. I haven’t broken Secure Boot and neither would I want to. I’m not giving malware writers or even the many writers of rude commercial software some new technique for getting a driver loaded onto their targets’ computers. I can get my driver loaded onto my computer because I have configured Secure Boot to know that I own my computer and because Windows actually does have code for noticing. Because I cannot configure Secure Boot on your computer I cannot get my driver loaded onto your computer. There is no security issue here.
But there is more than a little contrivance. What I’ve done to produce this picture is certainly not a general solution for real-world use. It’s not even evidence, just experimental confirmation. If my understanding of the relevant Windows code is correct, then I predict this demonstration will work: and, look, it does! Moreover, as you will see soon enough if you stick with me through all the ingredients and steps, it’s not a demonstration that would easily be seen even as something to try except if you already know it will work and why.
Indeed, the demonstration would be difficult to stumble onto even with a licensed edition. Fair enough, too! It arguably should be no small thing to set up your Windows for self-signed drivers. But the licensing is extra trouble. To get the picture without contrivance, you would need a license upgrade from Microsoft. The license data that would have to be upgraded is protected in various ways from being tampered with, and I certainly do not mean for anyone to try changing it, even for testing. If you want that this should work for you without contrivance, then pester Microsoft for an upgrade of the license data or at least for credible, detailed reasoning of why signing your own drivers so that they execute on your own computers but not on anyone else’s is something you need to be specially licensed for.
Still, even with contrivance, how can it be that I—or you after following my directions if you want to test what I say—can get anywhere near to producing such a picture? After all, for what looks to be Microsoft’s definitive statement of Driver Signing Policy Microsoft states plainly that “Starting with Windows 10, version 1607, Windows will not load any new kernel mode drivers which are not signed by the Dev Portal.” Just as plainly, my driver is loaded without having any signature from Microsoft. The same page also presents some exceptions, but only for cross-signed drivers, which mine can’t be unless I’ve somehow managed to synthesise valid Microsoft cross-certificates for My Own signatures. What my driver is signed with is what the similar page Kernel-Mode Code Signing Requirements calls an in-house test certificate. It shouldn’t load on any 64-bit Windows all the way back to Windows Vista except if Windows is started with some such boot option as testsigning or is run under a kernel-mode debugger, both of which are disabled by Secure Boot. By any reckoning from anything that Microsoft documents about kernel-mode driver signing, the picture is impossible.
But if the picture’s not faked, then its mere existence shows that what Microsoft says about kernel-mode driver signing is not entirely truthful. That doesn’t mean that Microsoft must know its documentation is not the whole truth. The obvious alternative explanation is that I have exploited a previously unknown coding error and thus exposed a security flaw, and am being reckless by not helping Microsoft to gloss over it via their programme of Coordinated Disclosure. Yet I have said already, and will say again and again, that there is no security issue here. What, really, is the truth?
The whole truth to how Windows validates kernel-mode drivers is one of the more surprising mysteries about Windows. For something that’s so vital to the integrity of Windows, has been around for over a decade, and has all its code concentrated into relatively few places where it surely has been picked over by numerous security researchers and reverse engineers, both with good and bad intent, there is strikingly little published material that looks reliable even in limited contexts and none that’s anywhere near to comprehensive. This is to nobody’s credit, including mine, for I count myself as supremely capable at the sort of work that uncovers definitive truth about Windows.
For the limited context here, just of explaining how Windows implements custom driver signing, I simply ignore a host of what I might otherwise regard as delicious opportunities for delving into detail. As examples, put aside all thoughts of nested signatures and support for old hashing algorithms, and don’t imagine that I mean this article’s summary as accounting for such things as the EKUs that are needed in signatures for HAL extensions and ELAM drivers else the signature isn’t even considered for the validation that I’m about to sketch.
With enough such caveats and more than a bit of hand-waving, validation of a signature on a kernel-mode driver may usefully be seen in three parts acting in two stages:
The first two parts are alternatives. Every signature gets validated through one or the other, never both. A signature that fails this first stage is rejected. Otherwise, it passes through to the second stage. On this point, we’re as well to get something out of the way from the start because Microsoft has sometimes described System Integrity policy files as giving “full control over allowed code in both kernel and user mode.” That, for example, is from Microsoft’s page Windows Defender Device Guard: virtualization-based security and Windows Defender Application Control, though only from an archiving site, Microsoft apparently having thought better of the claim round about April 2018. System Integrity policies do not give full control over what drivers Windows will load. They can not. They get to act only on what has been let through from the validation’s first stage. This mostly means that Microsoft’s default validation gets first go and System Integrity policy files can then filter from what Microsoft allows.
Also in need of sorting out from the start are what these policy files are properly named. I shall persist in calling them Secure Boot and System Integrity policies because these look to be the original names as used by Microsoft’s programmers who wrote the code that interprets the policies. These are the names that survive not only in what the public symbol files tell us of internal routines and variables but also in the text of error messages, in the names that the files must have, e.g., SecureBootPolicy.p7b and SiPolicy.p7b, and in the names that header files from Microsoft’s programming kits assign to relevant error codes, as with STATUS_SECUREBOOT_POLICY_NOT_SIGNED (0xC0430005) and STATUS_SYSTEM_INTEGRITY_POLICY_NOT_SIGNED (0xC0E90004).
For Secure Boot policy files, there are no alternative names to contend over: Secure Boot policy files appear to be completely undocumented. Microsoft does document the existence of System Integrity policies and even some of what can be done with them and how, but even if we put aside that the documentation is barely fit to acknowledge, we have the problem that Microsoft keeps changing the name. Code Integrity policies, Device Guard policies, and even Windows Defender Application Control (WDAC) policies all appear to name the same thing. The last, despite being Microsoft’s latest preference, is conspicuously unhelpful for disguising its applicability to kernel-mode drivers.
WORK IN PROGRESS TO BE FILLED IN HERE SOMETIME!
A Secure Boot policy file may specify signers in what Microsoft apparently names registry rules. It need not, though, and the default Secure Boot policy that’s in the boot manager’s resources does not. But if the active Secure Boot policy does specify signers (rather than just options and BCD rules), then signatures that are allowed by the policy get through to the next stage and those that are denied get no further. Nothing that Microsoft documents about driver signing, including requirements that drivers be signed by Microsoft, applies when validation is in the hands of a Secure Boot policy’s specification of signers.
Incidentally, the registry rules give very fine control, including to allow different signers in different scenarios, e.g., with HAL extensions distinguished from ELAM drivers and both distinguished from ordinary drivers, but they are limited just to signers.
Especially now that recent releases of Windows 10 recognise Secure Boot policy files only if signed by Microsoft, the first stage of signature validation is ordinarily left to a default implementation. This is what Microsoft documents some of. It too may usefully be seen, again with hand-waving, in two stages:
The older part is that a signature is valid only if it has an accepted root certificate. What counts as accepted varies with circumstances in ways whose intricacies, however important in other contexts, I merely sketch. In general, the accepted roots are a selection of Microsoft root certificates whose names and public keys are hard-coded into the boot manager, boot loader and CI. The selection is narrowed in some circumstances, e.g., to eliminate the Microsoft Code Verification Root that acts as the root for cross-signed drivers. It can even be restricted to just two or three of the latest Microsoft roots. At the other extreme, the BCD option testsigning is supported by widening the selection to take in two catch-all categories for signatures that do not have known roots (and which need have nothing at all to do with Microsoft).
A signature that is subject to the default implementation but does not have a root that’s accepted for the circumstances is rejected, and that’s that. This is where self-signed drivers are ordinarily rejected. If the accepted roots have been widened to allow either of those catch-all categories, then the signature is let through to the next stage. This allows self-signed drivers when testsigning is enabled.
Otherwise, the default implementation continues into its newer part which tests the most of what Microsoft documents as the new requirements for Windows 10. Policy options and registry settings, whether Secure Boot is enabled, and the signature itself all have a part in deciding whether the signature must be a so-called WHQL signature. If it is not required to be, then no matter how it got through to this stage, it’s let through to the next. When Windows 10 allows a cross-signed driver, this is where it got exempted from the new requirements. Of course, Windows 10 does ordinarily require WHQL signatures here. A signature that is not a WHQL signature gets rejected, and that’s that. This is where cross-signed drivers are ordinarly rejected. What exactly it means to be a WHQL signature is yet another point whose details I leave alone by appealing to this examination’s limited context (but which I do hope to present elsewhere).
Among the unsatisfactory implications when Microsoft introduced driver-signing requirements for 64-bit Windows Vista was that no way was provided for testing just some specified drivers. Enabling testsigning didn’t just allow Windows to load the drivers that you wanted to test but also any driver that had any sort of signature. Until your driver was ready to sign with an SPC (and cross-certificate) or to send to Microsoft, testing it meant exposing your computers to all comers. That’s not much of a problem in the early development, when the computers for testing are few and well-known, and are perhaps anyway run under a kernel-mode debugger, but it always was impractical for later testing in real-world conditions before public release. More than a few drivers that weren’t quite ready yet will have been signed with an SPC for internal use and then have escaped into general release.
Even drivers that are signed with an SPC as being ready for public distribution will include some that are seriously defective. Indeed, more than a few have presented severe compromises of security, as when anti-malware vendors have a driver expose a supposedly helpful I/O Control interface for a companion application to write wherever it wants in kernel-mode address space (apparently without having thought how helpful this would be to the malware). Such defects and abuses inevitably happen also with drivers that are signed by Microsoft, even after passing a full suite of WHQL tests—which, after all, can only test for what Microsoft knows to test for in circumstances that Microsoft can arrange for its test suite to create.
System Integrity policy files are in this context a very welcome, and long overdue, refinement. They give system administrators the means to express that some driver is not wanted on their computers, no matter that Microsoft signed it. They allow for specifying a driver as acceptable, no matter that Microsoft hasn’t signed it—and to do so without being exposed to all drivers that Microsoft hasn’t signed. The wonder is that Microsoft didn’t provide for some such specification from the beginning.
Even without the need to work around Microsoft’s licensing constraints, configuring Microsoft’s support for custom kernel-mode driver signing would not be straightforward. Depending on how you have already regarded what Secure Boot means for the security of your computers, you may already be well set up for it. More likely, though, you are not.
Just for basic applicability, you will need administrative access to a computer that has Secure Boot enabled and which runs Windows 10 in at least its 1703 release.
How much later a release this demonstration continues to apply to depends on when Microsoft rethinks what Microsoft allows and how. Microsoft could rework the custom driver signing feature so completely that this demonstration becomes nothing but a historical curiosity. Even if Microsoft retains the feature’s present architecture, a future release could defeat the contrivance so that no demonstration is possible without patching Microsoft’s code (which I might entertain to make the point) or cracking the license (which I do not want even to investigate). The best outcome, of course, is that a future release removes the licensing constraint and thus allows the demonstration to be simplified because contrivance becomes unnecessary. We can all dream!
The first prerequisite beyond the basics is that your computer must have as its Platform Key a certificate that gives you some signing authority. You have the obvious such authority if you can sign with the Platform Key itself. Less obvious, but very welcome, is that it’s enough if you can sign with some derived certificate, i.e., one whose chain of issuers includes the Platform Key on the way towards the root certificate.
Beware, though, that a Platform Key can satisfy Secure Boot and suffice for booting Windows but not be regarded by Windows as suitable for enabling the custom driver signing feature. One additional requirement is plainly by design. Whether it’s the Platform Key itself that you sign with or some derived certificate, your signature must meet modern notions of strength: no certificate in the chain can have a signature algorithm that is weaker than SHA-256. Perhaps not by design, either the Platform Key must be a root certificate or its immediate issuer must be. (The cause is that the relevant code checks for the Platform Key only when its examination of the signature reaches the end of the certificate chain, not along the way. But who’s to know whether this is the intended coding or an oversight?)
If only for now, I do not plan to help you here with how to create a suitable certificate or enrol it as the Platform Key. Indeed, if you did not immediately grasp that changing the Platform Key must include preserving (or appending to) other Secure Boot variables, then I strongly suggest you do not proceed with this demonstration until you have acquired from elsewhere a better knowledge both of Secure Boot itself and of how Microsoft uses it to secure Windows.
For the signing, you will need a tool since none comes with Windows as standard. Though I will use SIGNTOOL.EXE such as Microsoft supplies with the Software Development Kit (SDK) and Windows Driver Kit (WDK), you can use any that provides for specifying the type of content. For SIGNTOOL, this means you need its -p7co switch, as supported by versions from the kits for Windows 8 and higher.
Note that these first prerequisites are architectural. They are what you would need even if you had Microsoft’s license and they will still be needed if a future release retains the feature’s present architecture but no longer seeks to require a license. Your assertion of ownership via the Platform Key is what makes Microsoft’s support for custom kernel-mode signers secure. You can run your self-signed driver on your computer because Windows recognises your assertion of ownership. You can’t force the custom kernel-mode signing feature, let alone your particular driver, onto any computer that has Secure Boot enabled but with a Platform Key that you can’t sign with. And nobody can force either the feature or their driver onto your computer unless you’re careless with the private part of your Platform Key.
Note also that I wave my hands over what security this ideal future would make available when Secure Boot is not enabled. Less, obviously! Yet also more than you might expect. Subject to the same licensing constraint, the custom driver signing feature can be enabled even if Secure Boot is disabled. Even without the license, this demonstration can be varied to work without Secure Boot to show 64-bit Windows load a self-signed driver even though it’s supposed to require at least cross-signed drivers. But if Secure Boot is disabled, then with or without the license, the feature does not have the authority of the Platform Key and although I am convinced that it can be configured securely, I prefer for now to leave it alone.
A second prerequisite is that your computer’s boot configuration must allow for booting Windows from an alternate source. Most obvious, and henceforth assumed, is that your computer retains a recovery option that boots Windows on a RAM disk loaded from a recovery partition. For a quick reminder of whether Windows still knows how to boot this way, run bcdedit /enum osloader and look for a Windows Boot Loader entry whose description is something like Windows Recovery Environment.
Note that this second prerequisite is only for our contrivance while Microsoft means to require a license that no ordinary Windows users are known to have. If instead you work for some entity whose relations with Microsoft are so special that you have the license, then whether you think this circumstance is your blessing or your curse, this second prerequisite does not apply to you and you can skip all of this demonstration’s contrivances: if you control the Platform Key of a computer you work at, then custom driver signing is yours to set up right now.
To demonstrate a feature that lets you load a self-signed driver, you will of course need a self-signed driver to test with. To enable the feature, you’ll need a System Integrity policy file.
Though you need a driver and a policy file, you do not need any particular driver or policy file. Download mine to start with, but please remember that getting you started really is all that I mean them for. Change to your own the moment you’re ready to develop the demonstration nearer to what might be real-world practice were your use of the feature ever to be licensed or to need a license no longer.
I provide you with a very simple driver that I have signed with a certificate chain of my own which you should, of course, not trust:
Feel free to re-sign my driver with any certificate of your own. Feel free to change to a completely different driver. That you should be able to load your own driver on your own computer without a signature from anyone else is most of the point. Of course, to write your own driver you will need a Windows Driver Kit (WDK), which is easy to obtain, and a lot of specialised knowledge which is not.
For simplicity, I henceforth leave this option of replacement as understood: my further description of the demonstration assumes that you use my driver. It is named selfsign.sys and comes with a simple console application, named selfsign.exe, for testing that the driver works. The whole of the driver’s work is to create a device that is exposed to user mode as \\.\SelfSign and which implements a very bare Device I/O Control interface. The companion application reports whether the device is present and whether its interface responds as expected. The companion application needs no privilege for this and you do not need the companion application for anything. There are other ways to confirm that a driver is loaded. The companion application is just extra help.
Source code is provided not just because I always provide the source code but so that you can satisfy yourself that the driver does nothing more than what I say. The executables have been built from this source code using the WDK for Windows 7 (because it doesn’t require that you get a compiler and linker separately, because it comes with an MSVCRT.LIB that doesn’t tie you to any particular version of Visual Studio, and because its makefiles allow for relatively easy commenting of the build process). Build the executables any other way you want. However you build them, I leave it entirely to you to specify what to sign them with and how. For completeness, though, here are the commands that would have signed the executables that are in the zipfiles, given a simplified setup in which my certificates are in my Personal Store:
signtool sign -v -fd sha1 -s my -n "My Own SHA1 Driver Signer" selfsign.sys signtool sign -v -as -fd sha256 -s my -n "My Own Driver Signer" selfsign.sys
I provide you with a very simple policy that allows all signed drivers much as does the testsigning boot option:
You should, of course, be unhappy about this policy’s laxity both in allowing all signed drivers and in not specifying an update signer. Its merit, and the only reason I tolerate it, is that you can try the demonstration without needing to change the policy file. I could, of course, give you a policy file that you must change, and give you directions for the change. That I don’t is not just for simplicity. It is instead because if you want to change the policy file, as you certainly would for real-world use, then there is a snag. Microsoft provides that these policy files are prepared in XML and then converted to binary form by the PowerShell command ConvertFrom-CIPolicy. The helpfulness of this, however, is greatly reduced because the command declines to run on most Windows editions, not that Microsoft’s documentation of ConvertFrom-CIPolicy troubles to warn you. Just to prepare your own policy file, not necessarily on the computer you use for the demonstration, you will need one of the acceptable editions. What makes an edition acceptable is that the license value CodeIntegrity-AllowConfigurablePolicy is non-zero. Among Windows 10 consumer editions, Professional is not acceptable but Education is.
Quite why Microsoft has it that policy files affect all editions of Windows but can be edited (with Microsoft’s tools) only on some, I can’t begin to think. From Microsoft’s page Windows Defender Application Control, some such constraint is plainly by design:
WDAC policies can only be created on computers running Windows 10 Enterprise or Windows Server 2016. They can be applied to computers running any edition of Windows 10 or Windows Server 2016 and…
but just as plainly, no explanation is offered. Bizarre as it is, it is what it is, and it means I can’t count on your having a “right” Windows edition handy for editing the policy to make it more secure for you by being specific to your choices of driver and certificates.
Unless you want to edit the policy or satisfy yourself that the binary does indeed come from the XML, you need only the binary. It has been compiled by running the PowerShell command
ConvertFrom-CIPolicy -XmlFilePath selfsign.xml -BinaryFilePath selfsign.bin
If only for now, for the immediate purposes of this demonstration, the contents of the policy have to be left largely as magic. Since the source code is in XML, it is in some sense human-readable and I detect a strong suggestion that this is all the readability that Microsoft means anyone to have. For what’s allowed as the XML and with what syntax, read the XML schema in Microsoft.ConfigCI.Commands.dll, which is where the relevant PowerShell commands are handled. The magic, of course, is not in what elements are possible or in their syntax but in their meaning. I have not yet found formal documentation of this by Microsoft and I begin to expect none. After all, most of the applicable PowerShell commands edit the XML for you, as if what Microsoft wants you to know is not the meaning of the XML but the abstraction that is presented by the commands.
It’s not enough just to have a System Integrity policy file that’s syntactically valid and reads as if it might allow your signatures on your drivers. To enable the custom driver-signing feature, the file must first be signed with the Platform Key (or some derivation of it) and then installed with a particular name at a particular location.
As already noted, some flexibility is allowed about which certificate you sign the policy file with. Indeed, you can sign a syntactically correct policy file with anything and it can still be loaded as a policy file. I wave my hands over this because it’s not what we want. For Windows to recognise the policy file as enabling the custom driver signing feature when Secure Boot enabled, the policy must be signed by either the Platform Key or some certificate that has the Platform Key in its chain of issuers. Moreover, the last certificate in your signature—which, remember, will not ordinarily be the root of the certificate chain—must either be the Platform Key or be issued by the Platform Key. Suppose, for example, that My Own PK is installed as the Platform Key. Then custom driver signing will be enabled if you sign the policy file with My Own PK itself or with any certificate further to the right in either of the following chains
but not if you sign with any certificate in the chain
Whatever you sign the policy file with, you can do it with a single signtool sign command. I separate the command-line switches for clarity:
signtool sign -fd sha256 -p7co 220.127.116.11.4.1.311.79.1 -s my -n "My Own Policy Signer" -p7 . selfsign.bin
The -fd with its argument specifies the algorithm, which you know by now must be at least SHA-256.
The -p7co with its argument exactly as given is vital. We are not signing code but data, and even then some very particular content. The argument is an Object ID (OID) that goes into the signature such that whoever examines the signed file can know that what got signed, i.e., the content, is specifically a policy file. I know of no formal Microsoft documentation of this OID nor even of a programmatic declaration in a header from any development kit. Its first occurrence that I know of in formal documentation is almost incidental, as a magic argument in the recipe presented for Signing WDAC policies with SignTool.exe, apparently from 21st February 2018. Until then, documenting that it’s needed in the signature seems to have been left to the folklore of TechNet blogs:
Incidentally, where the first of these says you need “SignTool.exe, found in the Windows SDK (Windows 7 or later)”, do not take it literally: the version in the Windows 7 SDK does not know the -p7co switch and is therefore no good for signing System Integrity policies.
The -s and -n switches stand for whatever you devise for specifying your certificate and how to sign with it. My Own Policy Signer stands for the common name of your certificate that you sign with. For simplicity of demonstration, the switches I present assume that the certificate with its private key is in your Personal certificate store on the computer where you sign the policy. In real-world use you would certainly want to keep the private key much more securely than this. Again though, if that’s news to you, then you might better not be working through this demonstration!
The -p7 switch and its argument specify where to put the signed output, which gets named just by appending .p7 to the name of the unsigned input.
Put aside that Windows by now supports half a dozen types of policy file if you count by distinct values for the PolicyTypeID or a dozen if you count by distinct filenames. Settle just on the original policy type, which is the only one that Windows checks for a Platform Key signature. Whether signed or not, a policy of this type is recognised by Windows only if it is named SiPolicy.p7b. This too seems to be left to semi-official folklore (such as cited above). Ignore anything that any Microsoft blog tells you about putting it in the System32\CodeIntegrity subdirectory of the Windows installation that you want it to apply to. Now that the policy file is signed, it will be accepted by the boot loader only if the boot manager found it in the \EFI\Microsoft\Boot directory of the EFI system partition. This alternate location, though not its necessity, is at least hinted at by Microsoft in a design guide: Deploy Windows Defender Application Control policy rules and file rules.
The EFI system partition is ordinarily hidden. To have any access to it, you will need to mount a file system on it. To have convenient access, you will also want to assign a drive letter to it. Windows comes with a few suitable tools as standard. Perhaps the easiest is to run the following from an administrative Command Prompt:
mountvol x: /s
The /s switch both mounts the appropriate file system (which will be FAT32) and assigns it your choice x of drive letter. Ignore that what seems to be Microsoft’s documentation of this mountvol command, though dated 16th October 2017, says today, 17th June 2018, that /s is “Available on Itanium-based computers only.”
Now that you have access, simply copy the signed policy file into place, e.g.,
copy selfsign.bin.p7 x:\EFI\Microsoft\Boot\SiPolicy.p7b
In an ideal world, you might restart Windows now and then install your driver by any means you like. If the policy has been accepted as enabling custom kernel signing, then your non-Microsoft signature on your driver will be accepted. For us, however, the policy does not get accepted for this purpose except if we restart Windows in a very contrived way to work around Microsoft’s licensing constraints. Even with this contrivance, the policy will have been accepted only by the boot loader, not by the CI.DLL that handles code integrity once the kernel starts loading drivers. We can sneak past the licensing constraint only for a boot driver. To see it get loaded, we would have to restart yet again, repeating the contrivance. Instead, install now.
First, copy the driver to the usual directory for drivers, e.g., by running the following at an administrative Command Prompt:
copy selfsign.sys c:\windows\system32\drivers
A single sc create command will install the driver suitably. Again for clarity, I separate the arguments:
sc create SelfSign binPath= c:\windows\system32\drivers\selfsign.sys start= boot type= kernel group= "Boot Bus Extender"
The price for installing the driver with such ease is an objection by the Program Compatibility Assistant to tell you that “A digitally signed driver is required” and that “Windows has blocked the installation of a digitally unsigned driver.” The complaint is not only reasonable but desirable, even if it might more helpfully describe the driver not as unsigned but as having a signature that Windows disapproves of. If you prefer not to see the complaint, create the corresponding registry key and values manually. If I have to tell you what they are, then yet again it might be better if you don’t continue with the demonstration until you know more.
Restart Windows. Well, that’s what you’d do if you either had Microsoft’s license or (in some ideal future) didn’t need it. Instead, take a detour through a separate installation of Windows where you can run a Command Prompt. It doesn’t matter how you get there. That you’re working through this demonstration, presumably because you have an interest in deploying kernel-mode device drivers, if not in writing them, very likely means you have needed such a Command Prompt before as a recovery option and already have a favoured method. Modern Windows helpfully saves you from having to press some key at just the right time as Windows restarts. Go to Settings and Recovery and select to “Restart now” for an “Advanced startup”. At what looks like a boot menu, choose to Troubleshoot and then select “Advanced options” and Command Prompt. The computer will then reboot but start an alternate Windows on a drive x: that is in fact a RAM disk.
At the Command Prompt in this alternate Windows, run the Registry Editor. Select HKEY_LOCAL_MACHINE and then from the File menu ask to Load Hive. Browse to the SYSTEM hive of the Windows on which you want to load your self-signed driver. Most likely, this hive is C:\Windows\System32\config\SYSTEM. When you have loaded it, e.g., as system_c, browse to
Edit this value’s data to be 1. Close the Registry Editor, exit from the Command Prompt, and Continue with restarting your intended installation of Windows.
All being well, Windows restarts exactly as normal except that it loads your driver that you didn’t send to Microsoft for a signature. See that Windows is not in any sort of Test Mode. Run PowerShell’s Confirm-SecureBootUEFI command to see that Secure Boot has stayed on. Of course, what you most want to confirm is that your driver actually did get loaded. An easy way to check is through the Service Control Manager. If you have used my driver, the test will be that the command
sc query SelfSign
says the queried service is in fact a KERNEL_DRIVER that’s in the RUNNING state. Also if you used my driver, you can run its selfsign application to see if the driver is not just running but does what very little is expected of it:
Device \\.\SelfSign is present API version is 1.0
This happy circumstance of your having your own driver executing despite its having your own signature will persist through sleeps and hibernations until you next restart Windows. Remember first to disable your driver unless you plan to repeat the contrived detour into a separate Windows to edit the registry—or unless you want to check, and I do encourage you to check, that Windows otherwise would refuse to load the driver.
Of course, if something is wrong with your policy file or your signature of it or with your driver or your signature of it, then you will get nowhere near to any of this. The boot loader will itself have bounced you back to a recovery option, perhaps attempting to see what it might repair but then leaving you to fix things up at that same Command Prompt in a separate Windows installation. If you believe that you have followed my directions, then write to me and I will try to help as much as I can without getting into the business of writing policy files for your particular use. Please remember, though, that I can’t even begin to know what has happened on your computer without your sending me at least your signed policy, your signed driver and also the contents of the PK variable from Secure Boot.