Advertisement






Microsoft Windows 10 CiSetFileCache TOCTOU Security Feature Bypass

CVE Category Price Severity
CVE-2021-40484 CWE-862 Not specified High
Author Risk Exploitation Type Date
Baijiu High Local 2017-11-22
CPE
cpe:cpe:/o:microsoft:windows_10
CVSS EPSS EPSSP
CVSS:8.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2017110120

Below is a copy:

Microsoft Windows 10 CiSetFileCache TOCTOU Security Feature Bypass
Windows: CiSetFileCache TOCTOU Security Feature Bypass
Platform: Windows 10 10586/14393/10S not tested 8.1 Update 2 or Windows 7
Class: Security Feature Bypass

Summary:
Its possible to add a cached signing level to an unsigned file by exploiting a TOCTOU in CI leading to to circumventing Device Guard policies and possibly PPL signing levels.

Description:

Windows Code Integrity has the concept of caching signing level decisions made on individual files. This is done by storing an extended attribute with the name $KERNEL.PURGE.ESBCACHE and filling it with related binary information. As the EA name is a kernel EA it means it cant be set by user mode code, only kernel mode code calling FsRtlSetKernelEaFile. Also crucially its a purgeable EA which means it will be deleted automatically by the USN journal code if any attempt is made to write to the file after being set. 

As far as I can tell the binary data doesnt need to correspond to anything inside the file itself, so if we replace the contents of the file with a valid cached signing level the kernel is entirely relying on the automatic purging of the kernel EA to prevent spoofing. To test that theory I copied the EA entry from a valid signed file onto an unsigned file with a non-kernel EA name then used a disk editor to modify the name offline. This worked when I rebooted the machine, so I was confident it could work if you could write the kernel EA entry. Of course if this was the only way to exploit it I wouldnt be sending this report.

As user mode code cant directly set the kernel EA the facility to write the cache entry is exposed through ZwSetCachedSigningLevel(2). This takes a number of arguments, including flags, a list of associated file handles and the target handle to write the EA to. There seems to be 3 modes which are specified through the flags:

Mode 1 - This is used by mscorsvw.exe and seems to be used for blessing NGEN binaries. Calling this requires the caller to be a PPL so I didnt investigate this too much. Im sure theres probably race conditions in NGEN which could be exploited, or ways to run in a PPL if youre admin. The advantage here is you dont need to apply the cache to a signed file. This is what piqued my interesting in the first place.
Mode 2 - Didnt dig into this one TBH
Mode 5 - This sets a cache on a signed file, the list of files must only have 1 entry and the handle must match the target file handle. This is the one well be exploiting as it doesnt require any privileges to call.

Looking through the code inside the kernel the handles passed to ZwSetCachedSigningLevel are also passed as handles into CiSetFileCache which is slightly odd on the face of it. My first thought was you could race the handle lookup when ObReferenceObjectByHandle is called for the target handle and when the code is enumerating the list of handles. The time window would be short but its usually pretty easy to force the kernel to reuse a handle number. However it turns out in Mode 5 as the handle is verified to be equal the code just uses the looked up FILE_OBJECT from the target handle instead which removes this issue (I believe).

So instead I looked at racing the writing of the cache EA with the signature verification. If you could rewrite the file between the kernel verifying the signature of the file and the writing of the kernel EA you could ensure your USN journal entries from the writes are flushed through before hand and so doesnt cause the EA to be purged. The kernel code calls FsRtlKernelFsControlFile with FSCTL_WRITE_USN_CLOSE_RECORD to force this flush just before writing the EA so that should work. 

The question is can you write to the file while youre doing this? Theres no locking taking place on the file from what I could tell. There is a check for the target file being opened with FILE_SHARE_WRITE (the check for FileObject->SharedWrite) but thats not the same as the file handle already being writable. So it looks like its possible to write to the file.

The final question is whether theres a time period between signature verification and applying the EA that we can exploit? Turns out CI maps the file as a read only section and calls HashKComputeImageHash to generate the hash once. The code then proceeds to lookup the hash inside a catalog (presumably unless the file has an embedded signature). Therefore there's a clear window of time between the validation and the setting of the kernel EA to write.

The final piece of the puzzle is how to win that race reliably. The key is the validation against the catalog files. We can use an exclusive oplock to block the kernel opening the catalog file temporarily, which crucially happens after the target file has already been hashed. By choosing a catalog we know the kernel will check we can get a timing signal, modify the target file to be an unsigned, untrusted file then release the oplock and let the kernel complete the verification and writing of the cache. 

Almost all files on a locked down system such as Win10S are Microsoft Platform signed and so end up in catalogs such as Microsoft-Windows-Client-Features-Package. This seems like a hot-path file which might always be opened by the kernel and so couldnt be exploited for an oplock. However another useful feature now comes into play, the fact that theres also an EA which can specify a hint name for the catalog the file is signed in. This is called $CI.CATALOGHINT and so isnt a kernel EA which means we can set it. It contains a UTF8 encoded file name (without path information). Importantly while CI will check this catalog first, if it cant find the hash in that catalog it continues searching everything else, so we can pick a non-hot-path catalog (such as Adobes Flash catalogs) which we can oplock on, do the write then release and the verification will find the correct real catalog instead.  I dont think you need to do this, but it makes it considerably more convenient.

Note that to exploit this youd likely need executable code already running, but we already know theres multiple DG bypasses and things like Office on Win10S can run macros. Or this could be used from shellcode as I cant see any obvious limitation on exploiting this from a sandbox as long as you can write a file to an NTFS drive with the USN Change Journal enabled. Running this once would give you an executable or a DLL which bypasses the CI policies, so it could be used as a stage in an attack chain to get arbitrary code executing on a DG system.

In theory it think this would also allow you to specify the signing level for an untrusted file which would allow the DLL to be loaded inside a PPL service so you could use this on a vanilla system to try and attack the kernel through PPLs such as CSRSS as an administrator. I dont know how long the cache is valid for, but its at least a day or two and might only get revoked if you update your system or replace the file.

Proof of Concept:

Ive 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 youll need to sign the PoC (and the NtApitDotNet.dll assembly) so itll run. Or use it via one of my .NET based DG bypasses, in that case you can call the POC.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.0.8 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 Im using for a timing signal is already open then the oplock is never broken. Wait a period of time and try again. Also the output file must be on to a NTFS volume with the USN Change Journal enabled as thats 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 with valid Microsoft signature.


Source:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1332

Copyright ©2024 Exploitalert.

All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use.