[Beta 探究] 如何解除 Flightsigned Build 的时间炸弹

What Is “Flightsigning”

Flightsigning describes the process of digitally signing files with flight certificates. There are 4 common types of certificates used by Microsoft to sign Windows system files:

  • Prod – PCA 2010
  • PreProd – PCA 2011
  • Flight – Development PCA 2014
  • Test – a variety of things, most commonly MSIT Test CodeSign CA

Flight is just a particular certificate/series of certificates used by Microsoft, and when signed with a flight certificate, the build is called a flightsigned build.

Why “Flightsigned” Builds Cannot Boot on Current Date

The certificates used to sign early flightsigned builds explicitly specified expiration times, and were designed so that they become invalid as soon as the system time goes past the expiry time. This means Windows will treat system files digitally signed by those flight certificates as invalid once they expire.

This is a bit like how you cannot boot Windows normally with patched system files. A difference is, with signatures invalidated by modifications to the the binaries, you may use the NOINTEGRITYCHECKS BCD option to force the bootloader to continue booting, but with expired certificates, the bootloader will stop loading regardless.

This means the system time must not be more recent than the expiry time of those flight certificates, hence you cannot boot flightsigned builds on current date.

Why Am I Writing This Guide and Some Background

For the past 15 years of my life, I’ve been playing with betas of DOS-based and “classic” NT-based Windows. A week ago I suddenly decided to play with “modern” versions of Windows, because I know nothing about them. I believe debombing a Windows 10 beta build is a good way to start.

I picked the first official pre-release build of Windows 10 (build 9841) – what a mistake! I thought it might be the easiest build to debomb, and I ignored the fact that nobody even talked about debombing it. I then read that only fbl_partner_eeap builds may be debombed, because they contain “non-functional timebombs and as a result can be installed on the current date”. Well, I’d like to challenge the fact that only fbl_partner_eeap builds may be debombed, so I chose to continue with 9841 (a fbl_release flightsigned build).

Without a doubt, I overestimated my abilities and realized debombing a flightsigned build is like going through 9 circles of hell. Well, you see, I am talking about it so obviously I succeeded in the end. It was a long and tough battle, took me almost a week to fully defeat flightsigning.

Originally I patched the kernel to enable NOINTEGRITYCHECKS and then replaced all certificates in all digital signatures of all system files with a non-flight certificate taken from build 9838. Yep, it worked but the catalog files are also digitally signed with a flight certificate. I then went and replaced all catalogs with those from build 9838 – it didn’t work. Well, patches to ci.dll seems to be the only way left.

So in the end I patched ci.dll, then the Windows Loader (winload.exe/winload.efi) to load the patched ci.dll, and finally the Boot Manager to load the patched Windows Loader. It sounds easy but in reality it is much harder… well, not really that hard, just tedious. I patched 20 files just to safely get the x86 compile of Windows 10 build 9841 to boot on current date, and replacing all occurrences of those files in all indexes of boot.wim, install.wim and winre.wim took even more time. Since the process is so tedious, I am not going to patch every architecture of every flightsigned build, so I am just going to release a guide that will hopefully work for all flightsigned builds in all architectures.

Audience

This guide is for experienced readers only. To understand the procedures and perform the patches yourself, you must have a good understanding of the boot process of modern versions Microsoft Windows. You should also be able to understand C/C++ and the assembly language of the desired architecture.

If you do not have the ability to perform the patches yourself, I can patch them for you given that you upload the required files in the comments section below (since I am such a nice person). Currently I can only patch UEFI Boot Managers and Windows Loaders, because legacy BIOS ones are to time consuming to patch. You must upload ci.dll, winload.efi, winresume.efi, bootmgr.efi and bootmgfw.efi. Do not forget that the patches are only responsible for disabling the certificate expiry checks, you still need to ‘debomb’ the build by following this guide.

If you wish patch those files yourself, please go ahead and read the following sections.

Patching Windows to Boot on the Current Date

Since there are different architectures out there, and even for the same architecture, the patches may be different (different offsets, different registers), I will demonstrate the patches mainly with C/C++ pseudo-code.

ci.dll

The first file to patch is ci.dll, the Code Integrity Library responsible for signature validations. We are not going to completely remove signature validation, we are just going to make it ignore expired certificates.

There is a status called STATUS_IMAGE_CERT_EXPIRED, and we don’t want that to be returned. So, we need to patch functions that may return STATUS_IMAGE_CERT_EXPIRED. Since different builds have different number of functions capable of returning STATUS_IMAGE_CERT_EXPIRED, and those functions are probably all different, you should just search for 0x0C0000605 in ci.dll. When you find something like mov ???, 0C0000605h, check what causes 0C0000605h to be moved to the ??? register. Generally you should find an if-statement like this:

if ( condition )
{
  // certificate expired
  result = STATUS_IMAGE_CERT_EXPIRED;
}
// certificate not expired

We need to remove the if-statement, preferably by patching a conditional jump to become an unconditional jump or by NOP-ping out a conditional jump (depending on the situation). Make sure you find all instances of mov ???, 0C0000605h and patch them all.

CipReportAndReprieveUMCIFailure should also be patched just to be safe, since we are modifying system files. In there we need to make the code inside the if-statement (g_CiDeveloperMode & 1) != 0 execute regardless of the value of g_CiDeveloperMode.

if ( (g_CiDeveloperMode & 1) != 0 )
{
  // code here
  EventDescriptor = (const EVENT_DESCRIPTOR *)CiPolicyFailureIgnored;
  *(BYTE *)a10 = 1;
  // code here
}

You should find a conditional jump after the TEST instruction. Again, depending the the situation, you need to either make that unconditional or you NOP it out.

Extra Steps Without Symbols

For the g_CiDeveloperMode patch, you need to identify the CipReportAndReprieveUMCIFailure function first. To identify that function, search for the ASCII string “This break indicates this binary is not signed correctly” and jump to reference. Scroll up until you find a call to PsGetProcessProtection, and not far above that you should see a TEST instruction (test something, 10h). Once you find that, you should see another TEST instruction (test something, 1) above that one, and that TEST instruction is the (g_CiDeveloperMode & 1) != 0 if-statement. Now you patch the conditional jump right below it as you would with debug symbols.

bootmgr/bootmgr.efi, memtest.exe/memtest.efi, cdboot.efi

Since we patched ci.dll, we must patch the Windows Loader to load our patched ci.dll. There are two things to patch – the signature validity check (what causes the blue screen saying Windows cannot load xxx), and the boot option check (to prevent that Preparing Automatic Repair screen).

For the signature validity check, we need to remove the checks for the return value of the ImgpValidateImageHash function. You may also patch ImgpValidateImageHash itself to return zero, but I recommend that you patch the return value checks (patching a complex function to just return 0 may cause undesired effects). Locate the ImgpValidateImageHash function first, jump to reference, you should see the return value being moved to another register, and then a TEST instruction. You should patch the conditional jump under the TEST instruction to become an unconditional jump if it jumps outside of the if-statement, to avoid executing code inside the if-statement. If it jumps to the code inside that if-statement, then you should NOP the conditional jump out. What the code may look like:

result = ImgpValidateImageHash(something);
if ( result < 0 )
{
  result = ImgpFilterValidationFailure(something);
  if ( result < 0 )
  {
    if ( (something & 0x20) != 0 )
      goto Label;
    result = 0;
  }
}

Then you should patch BlImgQueryCodeIntegrityBootOptions to tell the Windows Loader to ignore ‘corrupted’ files, to avoid that “Preparing Automatic Repair” screen every time you boot Windows. You just need to make the second argument (IntegrityChecksDisabled, a PBOOLEAN) TRUE.

Extra Steps Without Symbols

To find the ImgpValidateImageHash function, search for the ASCII string "1.3.6.1.4.1.311.61.4.1", then jump to reference. To find the BlImgQueryCodeIntegrityBootOptions function, search for 0x16000048 until you see 0x16000049 not far below it, then continue to search for 0x16000048 until you see 0x16000049 below it again.

bootmgr/bootmgr.efi, memtest.exe/memtest.efi, cdboot.efi

The Boot Manager also must be patched in order to load the patched Windows Loader. The patch is identical to the Windows Loader patch, except you don’t need to patch BlImgQueryCodeIntegrityBootOptions. The hardest part is modifying the compressed bootmgr file for legacy BIOS booting. If you are using UEFI, then you’re all done once you finish patching those Boot Manager .EFI files.

To patch the compressed bootmgr file, you first need to understand its structure. It can be broken into 4 parts – a raw 16-bit stub loader, a custom structure containing information about the compressed part, a stub PE file, and finally the compressed Boot Manager.

First step is to decompress it or find an uncompressed copy. You should be able to find a file called bootmgr.exe in boot.wim (WinPE), that file is identical to the Boot Manager file in bootmgr, except for not being compressed. You then need to follow the Boot Manager patch instructions above to patch it. Finally, you have to compress the patched executable and add it back to bootmgr. For compression, you may use joakim’s BOOTMGR Recompiler v2. It outputs a combined bootmgr with a hard coded 16-bit stub, so I strongly recommend that you extract the compressed part from the output file and replace the compressed part of the original bootmgr file with it. Of course you must also modify the custom structure below the 16-bit stub and the PE checksum of the PE stub. The custom structure is documented by jaokim so see the BOOTMGR Recompiler v2 project page for that. The PE checksum correction is a bit hard, so I decompiled some bootmgr signature checking functions and put together this. Just run it to print out the correct checksum, then change the PE checksum of the PE stub to the correct checksum.

Removing the Timebomb

Surprisingly… perhaps not, the wall of text above isn’t even related to debombing. If you patched all files correctly, you should now be able to boot Windows on the current date, but the timebomb is still there. Just like other pre-release builds of Windows 10, flightsigned builds can be debombed by following this BetaArchive guide. That guide is a bit confusing, so when I get time I will write a better one for BetaWorld.

Some Debombed Builds and Patched Files

Edit: Thanks to Zammis Clark for identifying errors in this article, they are now corrected. It is also suggested that the “best way” would probably be to fork EfiGuard to also patch the certificate expiry check in ci.dll, so that there will be no need to modify system files.