Thursday, 5 September 2024

HEVD-BufferOverflowNonPagedPoolNx LPE from Low Integrity

Hello everyone. Today, we are going to do the HEVD BufferOverflowNonPagedPoolNx  from Low Integrity , which means we cannot use Windows API like NtQuerySystemInformation  to leak the base address of ntoskrnl.exe. I am doing this writeup on windows 10 22H2 machine . 

Original Author : @ommadawn46 has explained this same exploit in his GitHub repo here: repo . I am trying to explain that more briefly in my way in this post. Thanks a lot to @ommadawn46 .

Good To Know:  As far as I know, This is the best paper for learning modern windows Heap i.e., Segment Heap . paper     If you have some time ,please read this paper but I will obviously try to explain as much as I can.

Source Code Review

You can download the source code from and compile the win10-klfh branch , then you can use OSR tool to load the driver.
We are analyzing the BufferOverflowNonPagedPoolNx.c file which has the vulnerability
NTSTATUS
BufferOverflowNonPagedPoolNxIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    SIZE_T Size = 0;
    PVOID UserBuffer = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    UNREFERENCED_PARAMETER(Irp);
    PAGED_CODE();

    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    if (UserBuffer)
    {
        Status = TriggerBufferOverflowNonPagedPoolNx(UserBuffer, Size);
    }

    return Status;
}
So, first here is the BufferOverflowNonPagedPoolNxIoctlHandler which takes the user supplied input and send the UserBuffer  and Size  to a function TriggerBufferOverflowNonPagedPoolNx
 NTSTATUS
TriggerBufferOverflowNonPagedPoolNx(
    _In_ PVOID UserBuffer,
    _In_ SIZE_T Size
)
{
    PVOID KernelBuffer = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try
    {
        DbgPrint("[+] Allocating Pool chunk\n");

        //
        // Allocate Pool chunk
        //

        KernelBuffer = ExAllocatePoolWithTag(
            NonPagedPoolNx,
            (SIZE_T)POOL_BUFFER_SIZE,
            (ULONG)POOL_TAG
        );
In this function, first it allocates buffer on NonPagedPoolNx  and size is 16 Bytes with PoolTag as Hack.
   if (!KernelBuffer)
        {
            //
            // Unable to allocate Pool chunk
            //

            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else
        {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPoolNx));
            DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
        }

        //
        // Verify if the buffer resides in user mode
        //

        ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);

#ifdef SECURE
        //
        // Secure Note: This is secure because the developer is passing a size
        // equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
        // Hence, there will be no overflow
        //

        RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
        DbgPrint("[+] Triggering Buffer Overflow in NonPagedPoolNx\n");

        //
        // Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
        // because the developer is passing the user supplied value directly to
        // RtlCopyMemory()/memcpy() without validating if the size is greater or
        // equal to the size of the allocated Pool chunk
        //

        RtlCopyMemory(KernelBuffer, UserBuffer, Size);
#endif

        if (KernelBuffer)
        {
            DbgPrint("[+] Freeing Pool chunk\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);

            //
            // Free the allocated Pool chunk
            //

            ExFreePoolWithTag(KernelBuffer, (ULONG)POOL_TAG);
            KernelBuffer = NULL;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}
Then, It does the basic checks that the buffer resides in Usermode that we send using the IOCTL. In the Vulnerable part , there is the bug : It copies the our usermode supplied buffer to kernel mode buffer that is allocated previously with our user supplied Size.

This is a Heap Buffer Overflow in NonPagedPoolNx.

To be Continued.....

No comments:

Post a Comment