Windows: CiSetFileCache TOCTOU CVE-2017-11830 Incomplete Fix

Issue description

Windows: CiSetFileCache TOCTOU CVE-2017-11830 Incomplete Fix
Platform: Windows 10 1709 (including Win10S)
Class: Security Feature Bypass
Summary:
The fix for CVE-2017-11830 is insufficient to prevent a normal user application adding a cached signing level to an unsigned file by exploiting a TOCTOU in CI leading to circumventing Device Guard policies.
Description:
The previous issue I reported was due to not checking for write access on the target file handle when setting the cache. This allows a user application to abuse a TOCTOU and rewrite the file after the hash has been generated for the file. The only changed code seems to be below:
FILE_OBJECT target_file;
ObReferenceObjectByHandle(FileHandle, 0, *IoFileObjectType, &target_file);
if (target_file->SharedWrite) {
return STATUS_SHARING_VIOLATION;
}
if (target_file->WriteAccess) { ← Additional check for the file being opened for write.
if ((PsGetProcessProtection(PsGetCurrentProcess()) & 7) != ProtectedProcessLight)
return STATUS_SHARING_VIOLATION;
}
The fix was to add a check that the target file passed isn’t writable. This combined with the check for FILE_SHARE_WRITE should mean the user can’t hold on to a writable file handle. However, when the file handle is converted to a file object with ObReferenceObjectByHandle the desired access is 0, which means we can pass a handle with any granted access including SYNCHRONIZE or READ_CONTROL, which do not respect file sharing. So we can still exploit this issue by doing the following:
1. Open the file for write access.
2. Reopen another handle to the file for SYNCHRONIZE access. This works as this access right can be used regardless of the sharing mode.
3. Set cached signing level through the handle opened in 2.
4. Wait for oplock, rewrite file using handle opened in 1. Release oplock.
Proof of Concept:
I’ve provided a PoC as a C# project. It will allow you to “cache sign” an arbitrary executable. If you want to test this on a locked down system such as Win10S you’ll need to sign the PoC (and the NtApitDotNet.dll assembly) so it’ll run. Or use it via one of my .NET based DG bypasses, in that case you can call the PoC_CacheSignature.Exploit.Run method directly. It copies notepad to a file, attempts to verify it but uses an oplock to rewrite the contents of the file with the untrusted file before it can set the kernel EA.
1) Compile the C# project. It will need to grab the NtApiDotNet v1.1.7 package from NuGet to work.
2) Execute the PoC passing the path to an unsigned file and to the output “cache signed” file, e.g. poc unsigned.exe output.exe
3) You should see it print the signing level, if successful.
4) You should not be able to execute the unsigned file, bypassing the security policy enforcement.
NOTE: If it prints an exception then the exploit failed. The opened catalog files seemed to be cached for some unknown period of time after use so if the catalog file I’m using for a timing signal is already open then the oplock is never broken. Just rerun the poc which will pick a different catalog file to use. Also the output file must be on to a NTFS volume with the USN Change Journal enabled as that’s relied upon by the signature level cache code. Best to do it to the boot drive as that ensures everything should work correctly.
Expected Result:
Access denied or at least an error setting the cached signing level.
Observed Result:
The signing level cache is applied to the file with no further verification. You can now execute the file as if it was signed.
This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public.