Using ZwProtectVirtualMemory with usermode address returns INVALID_PARAMETER_3

So I recently began coding kernel drivers and I’m trying to change the page protection of my usermode app to simulate shellcode injection using my custom kernel driver specifically from PAGE_EXECUTE_READ to PAGE_EXECUTE_READWRITE.

I decided to use ZwProtectVirtualMemory since I cannot use mdl’s because of it only working on kernelmode addresses.

The function that I am currently using is this:

//Takes the pid of the usermode process, the usermode address and the page size
NTSTATUS VirtualProtectMem(ULONG64 pid, PVOID address, ULONG pageSize)
{
//some basic checking for invalid values
if (!pid || !address || !size)
        return STATUS_INVALID_PARAMETER;

    NTSTATUS status = STATUS_SUCCESS;
    PEPROCESS process = nullptr;

    //Fetch the eprocess structure from the process id
    if (!NT_SUCCESS(PsLookupProcessByProcessId(reinterpret_cast<HANDLE>(pid), &process)))
        return STATUS_NOT_FOUND;

   //attach our thread to the usermode process adress space
    KAPC_STATE state;
    KeStackAttachProcess(target_process, &state);

    ULONG curProtection = NULL;//variable to store the previous page protection   

    //Attempting to change the page protection
    status = ZwProtectVirtualMemory(ZwCurrentProcess(), &address, &size, PAGE_EXECUTE_READWRITE, &curProtection);
    
    //Detach our thread from the user space
    KeUnstackDetachProcess(&state);

    if (NT_SUCCESS(status))
        KernelPrint("Succeeded");
    else
        KernelPrint("Failed");
    //Free the pointer to the EPROCESS structure
    ObDereferenceObject(process);
    return status;
}

However after calling this function with parameters such as (3fe4, 0000000100002B0F, 0x8), the ZwProtectVirtualMemory function returns an NTSTATUS of c00000f1 which means INVALID_PARAMETER_3.

Any ideas?

Thanks in advance

I’ll just state the seemingly obvious… INVALID_PARAMETER_3 means the function call doesn’t like the size parameter. If your example parameters are actually correct, you seem to be passing an 8 as the size, rather than a pointer to the variable that contains the 8. The code looks okay though, so perhaps that is just a simplification in your question. The function wants the range of memory to be within the usermode virtual address space, which it appears to be.

So if none of the above helps, I would say step into this in your debugger and see why the function is returning that status. :slight_smile:

@Jeremy_Hurren said:
I’ll just state the seemingly obvious… INVALID_PARAMETER_3 means the function call doesn’t like the size parameter. If your example parameters are actually correct, you seem to be passing an 8 as the size, rather than a pointer to the variable that contains the 8. The code looks okay though, so perhaps that is just a simplification in your question. The function wants the range of memory to be within the usermode virtual address space, which it appears to be.

So if none of the above helps, I would say step into this in your debugger and see why the function is returning that status. :slight_smile:

Thank you for your answer, however I was actually passing the address of the value 8. I fixed it though, the problem seemed to be with the buffer, I didn’t create a kernelmode buffer by allocating a pool, I directly took the buffer from usermode; I’m not sure if that was the origin of this error however. What do you think?

ZwProtectVirtualMemory function returns an NTSTATUS of 0xc00000f1 which means INVALID_PARAMETER_3

ZwProtectVirtualMemory returns INVALID_PARAMETER_3 in the following cases:
1 - (*BaseAddress + *RegionSize) > MM_HIGHEST_USER_ADDRESS - The region size is too big - the range is not in the user mode range.
2 - *RegionSize == 0 - the region size is zero.

Remember this function is undocumented, so there can be more cases in the future.

1 Like

@0xrepnz said:

ZwProtectVirtualMemory function returns an NTSTATUS of 0xc00000f1 which means INVALID_PARAMETER_3

ZwProtectVirtualMemory returns INVALID_PARAMETER_3 in the following cases:
1 - (*BaseAddress + *RegionSize) > MM_HIGHEST_USER_ADDRESS - The region size is too big - the range is not in the user mode range.
2 - *RegionSize == 0 - the region size is zero.

Remember this function is undocumented, so there can be more cases in the future.

Oh, thanks a lot I’ll keep this in mind :slight_smile: