logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

CVE-2025-13032: Entering and Breaking the Avast Antivirus Sandbox Part 2

Original article
safateam.com|96 points|26 comments|by safateam|Sep 25, 2026

CVE-2025-13032: Entering and Breaking the Avast Antivirus Sandbox Part 2

This technical deep-dive serves as the conclusion to our research into Avast Antivirus, specifically focusing on the full exploitation chain of CVE-2025-13032 within a fully patched Windows 11 environment.

📌 Executive Summary

Building upon the double-fetch vulnerability identified in Part 1, this article details the transition from a controlled paged pool overflow to a full arbitrary kernel read/write primitive. This was achieved by targeting and corrupting the RegBuffers array within the IORing object.

🛠️ Exploitation Roadmap

  • Trigger Double-Fetch Vulnerability
  • Execute Paged Pool Overflow
  • Implement Heap Spray Strategy
  • Leak Kernel Addresses via MDL Introspection
  • Corrupt IORing →\rightarrow→ RegBuffers
  • Achieve SYSTEM Privileges via Token Theft
  • Perform Cleanup to avoid BSOD (Blue Screen of Death)

⚠️ Important Mitigation Note

[!IMPORTANT] In current iterations of the Windows kernel and associated drivers, Microsoft has implemented user-mode accessors. These mechanisms verify every kernel-mode access to user-mode memory, ensuring that buffers truly reside in userspace at the moment of access.

This security enhancement effectively neutralizes prevents the specific exploitation technique detailed in this write-up. For a deeper technical explanation of this mitigation, refer to the official Microsoft documentation or this video analysis.


🔍 Bug Analysis: The Double-Fetch

The core of the vulnerability is a double-fetch flaw that facilitates a kernel pool overflow. The driver attempts to process a _UNICODE_STRING structure provided by the user. However, it retrieves the Length field multiple times from user-controlled memory, creating a race condition.

The Logic Flow

  1. First Fetch: The kernel reads Length to determine how much memory to allocate via ExAllocatePoolWithTag.
  2. Second Fetch: The kernel reads Length again to determine how many bytes to copy using memmove.

If an attacker can modify the Length value between these two operations, they can force the kernel to copy more data than was actually allocated.

Mathematical Representation of Allocation

The allocation size is calculated as: Total Allocation=Lengthfetch1+16 bytes\text{Total Allocation} = \text{Length}_{\text{fetch1}} + 16 \text{ bytes}Total Allocation=Lengthfetch1​+16 bytes

If Lengthfetch2>Lengthfetch1\text{Length}_{\text{fetch2}} > \text{Length}_{\text{fetch1}}Lengthfetch2​>Lengthfetch1​, the resulting memmove operation will write past the end of the allocated buffer.

Vulnerable Code Implementation

// Simplified representation of the vulnerable logic
void ProcessUnicodeString(UNICODE_STRING* unicodestring_user) {
    // Step 1: Validate memory locations
    ProbeForRead(unicodestring_user, 0x10, 1); 
    ProbeForRead(unicodestring_user->Buffer, unicodestring_user->Length, 1);

    // Call internal capture function
    sub_140071C6C(v18, v14[v23 + 13], unicodestring_user);
}

__int64 __fastcall sub_140071C6C(__int64 a1, _QWORD* a2, _UNICODE_STRING* unicodestring_user) {
    // FIRST FETCH: Length is used for allocation
    size_t alloc_size = unicodestring_user->Length + 16;
    void* PoolWithTag = ExAllocatePoolWithTag(PagedPool, alloc_size, 0x20786E53u);

    if (!PoolWithTag) return 0xC000009A;

    // Store length and setup buffer
    *PoolWithTag = unicodestring_user->Length;
    PoolWithTag[1] = unicodestring_user->Length;
    *((_QWORD*)PoolWithTag + 1) = (char*)PoolWithTag + 8;

    char* Buffer = unicodestring_user->Buffer;
    if (Buffer) {
        if (unicodestring_user->Length) {
            // SECOND FETCH: Length is fetched again for the copy operation
            // RACE CONDITION OCCURS HERE
            memmove((_OWORD*)PoolWithTag + 1, Buffer, unicodestring_user->Length);
        }
    }
    *a2 = PoolWithTag;
    return 0;
}

🏎️ Winning the Race

To trigger the overflow, we employ a multi-threaded approach. One thread invokes the driver function, while a second "attacker" thread runs in a high-frequency loop, toggling the Length field.

StateLength ValueKernel ActionResult
SafeSmall (e.g., 0x10)ExAllocatePoolWithTagSmall buffer allocated
MaliciousLarge (e.g., 0x1000)memmovePool Overflow

Race Condition Sequence

While the timing window is extremely tight, the race can be won reliably after a relatively small number of attempts.

🚀 Path to Privilege Escalation

The ultimate objective is to transform this overflow into a Local Privilege Escalation (LPE). The strategy involves:

  1. Heap Spraying: Preparing the paged pool to ensure the overflow hits a target object.
  2. Targeting IORing: Corrupting the RegBuffers array of an IORing object to gain an arbitrary read/write primitive.
  3. Information Leak: Using MDL (Memory Descriptor List) introspection to find the necessary kernel addresses.
  4. Token Theft: Locating the SYSTEM process token and copying it to the current process's token.
  5. Stability: Carefully repairing corrupted kernel structures to ensure the system does not crash during teardown.

By successfully executing these steps, we elevate the process privileges from a standard user to NT AUTHORITY\SYSTEM.