VirtualProtectEx, ERROR_INVALID_PARAMETER (ошибка 87)

Я пытаюсь написать функцию, которая будет использовать память процесса. Я заметил, что ReadProcessMemory не работает в регионах с разрешениями, установленными на PAGE_NOACCESS или PAGE_GUARD. Я решил, что буду использовать VirtualProtectEx, чтобы временно изменить разрешения на этих страницах, чтобы я мог их читать. Казалось, что это работает в большинстве случаев, но всегда было несколько случаев, когда VirtualProtectEx не работал с ERROR_INVALID_PARAMETER. Я трижды проверил все параметры, и они кажутся правильными, и даже добавил код, который снова вызывает VirtualQueryEx в случае сбоя, чтобы убедиться, что переданные параметры все еще действительны. Что вызывает это и как я могу это обойти? Я добавил немного минимального (настолько минимального, насколько я мог его получить) кода ниже, воспроизводящего проблему.

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;
}

2 ответа

Вы не можете изменить разрешения этой страницы.

С помощью

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

все, что вам нужно сделать, чтобы отфильтровать плохую память, вам не нужно изменять разрешения для страниц с другими состояниями и защитами. "Скрытые данные" на других типах страниц будут ЧРЕЗВЫЧАЙНО редкими, как шанс 0,000001%, и не стоит даже думать, если у вас нет причин подозревать это в случае работы над каким-то очень продвинутым механизмом защиты.

В ответ на GuidedHacking для будущих людей, которые ищут правильную информацию.

Страницы с защитой от выполнения/чтения все еще могут быть возвращены и должны быть изменены, например, на PAGE_EXECUTE_READWRITE для записи. Код тоже неверный. Это эффективно сравнивает MEM_COMMIT с защищенным. Очевидно, это не то, что хотел бы сделать любой, сканирующий процесс на наличие полезной памяти.

Исправлено и упрощено:

      if (state == MEM_COMMIT && !(protect & (PAGE_NOACCESS|PAGE_GUARD))
Другие вопросы по тегам