kansas_bulldog382
kansas_bulldog382

Reputation: 83

VirtualProtectEx, ERROR_INVALID_PARAMETER (error 87)

I am trying to write a function that will can through a process' memory. I noticed that ReadProcessMemory would fail on regions with permissions set to PAGE_NOACCESS or PAGE_GUARD. I decided that I would use VirtualProtectEx to temporarily change the permissions on these pages so I would be able to read them. This seemed to work in most cases, but there would always be a few cases in which VirtualProtectEx would fail with ERROR_INVALID_PARAMETER. I triple-checked all the parameters and they seem to be correct and I even added code which would call VirtualQueryEx again on failure to ensure the parameters passed were still valid. What is causing this and how can I get around it? I've added some minimal (as minimal as I could get it) code below that reproduces the problem.

int protect_test(DWORD pid) {

    HANDLE phandle;

    struct _MEMORY_BASIC_INFORMATION mbi;
    SIZE_T mbi_size = sizeof(struct _MEMORY_BASIC_INFORMATION);
    DWORD state;
    SIZE_T regionsize;

    int bytes_retrieved;
    void* lpAddress;
    void* lpBaseAddress;
    void* lpAddress2;
    int error;
    struct _SYSTEM_INFO lpSystemInfo;
    DWORD pagesize;

    DWORD protect;
    DWORD newprotect;
    DWORD lpflOldProtect;
    DWORD lpExitCode = 0;

    // get the page size
    GetSystemInfo(&lpSystemInfo);
    pagesize = lpSystemInfo.dwPageSize;

    // get handle to process
    if ((phandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)) == NULL) {
        return(-1);
    }

    // main loop
    lpAddress = 0;
    while (!((bytes_retrieved = VirtualQueryEx(phandle, lpAddress, &mbi, mbi_size)) == 0 && (error = GetLastError()) == ERROR_INVALID_PARAMETER)) {

        // Check for error -2
        if (GetExitCodeProcess(phandle, &lpExitCode) && lpExitCode != 259) {
            // process was closed abruptly 
            return -2;
        }

        // handle VirtualQueryEx fail
        if (bytes_retrieved == 0) {
            lpBaseAddress = lpAddress;
            lpAddress2 = (unsigned long long) lpAddress + pagesize;
            lpAddress = lpAddress2;
            continue;
        }

        // set variables so we don't have to refernce mbi directly 
        lpBaseAddress = mbi.BaseAddress;
        regionsize = mbi.RegionSize;
        lpAddress2 = (unsigned long long)lpBaseAddress + regionsize;
        state = mbi.State;
        protect = mbi.Protect;


        if ( state == MEM_COMMIT && ((protect & PAGE_NOACCESS) || (protect & PAGE_GUARD)) ) {

            // some debug print
            //printf(" State: 0x%x Protection: 0x%x Regionsize: 0x%llx %p - %p\n", state, protect, regionsize, lpBaseAddress, (unsigned long long)lpAddress2 - 1);

            // The problematic VirtualProtectEx call
            newprotect = PAGE_EXECUTE_READWRITE;
            if (VirtualProtectEx(phandle, lpBaseAddress, regionsize, newprotect, &lpflOldProtect) == NULL) {
                printf("   Failed to change region's protection to 0x%x. Base address: 0x%p Errorcode: 0x%x\n", newprotect, lpBaseAddress, GetLastError());
                printf("   VirtualQuery returns %d. The base address returned was 0x%o. The regionsize returned is 0x%llx\n", VirtualQueryEx(phandle, lpBaseAddress, &mbi, mbi_size), mbi.BaseAddress, mbi.RegionSize);
                return(1);
            }

            // set things back
            if (VirtualProtectEx(phandle, lpBaseAddress, regionsize, lpflOldProtect, &lpflOldProtect) == 0) {
                printf("   Failed to change region's protection back to its previous state\n", pid);
            }

        }

        // update lpAddress
        lpAddress = lpAddress2;
    }

    return 0;
}

Upvotes: 2

Views: 4622

Answers (2)

user17466308
user17466308

Reputation:

In response to GuidedHacking for future people looking for correct info.

Pages with execute/read protection may still be returned and will need to change to e.g. PAGE_EXECUTE_READWRITE for writing. The code is also wrong. It's effectively comparing MEM_COMMIT together with protected. Obviously not what anyone scanning a process for useful memory would want to do.

Corrected and simplified:

if (state == MEM_COMMIT && !(protect & (PAGE_NOACCESS|PAGE_GUARD))

Upvotes: 0

GuidedHacking
GuidedHacking

Reputation: 3923

You can't change that page's permissions.

Using

if ( state == MEM_COMMIT && ((protect & PAGE_NOACCESS) || (protect & PAGE_GUARD)) )

to filter out bad memory is all you need to do, you do not need to change permissions to pages with other states and protections. "Hidden data" in other page types would be EXTREMELY rare like a 0.000001% chance, not even worth considering unless you have a reason to suspect it in the case of working on some very advanced protection mechanism.

Upvotes: 0

Related Questions