0

I'm trying to do some tests on page fault exception on Windows. The requirement is to put some data into the page boundary so that reading the data will trigger a page fault exception.

Concretely, the test consists of 7 bytes (for example) which are logically consecutive and allocated. I need the first 5 bytes to be located on a physically allocated page, but the next 2 bytes are located on a page which is not yet physically allocated.

So that reading this 7 bytes will trigger a page fault exception, while reading only 4 bytes will not.

I intially think that I should allocate two pages, write 7 byte data over the boundary, and page out the second page.

Can I do this using some user-mode Windows API?

Ta Thanh Dinh
  • 638
  • 1
  • 5
  • 12

2 Answers2

1

Yes, this is possible in user-land. You'd usually reserve all the pages at once with VirtualAlloc and then commit part of it. You can change the protection back and forth with VirtualProtect after committing.

#include <cstdint>
#include <iostream>
#include <Windows.h>

int main()
{
    //Retrieve currently configured page-size
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    DWORD pageSize = info.dwPageSize;
    //Reserve 2 pages
    void *mem = VirtualAlloc(NULL, pageSize*2, MEM_RESERVE, PAGE_NOACCESS);
    //Commit first page with read/write premissions
    VirtualAlloc(mem, pageSize, MEM_COMMIT, PAGE_READWRITE);
    //get pointer with 5 bytes in the first page
    uint8_t* ptrAcross = (uint8_t*)mem + pageSize - 5;
    //Fill first 5 bytes
    FillMemory(ptrAcross, 5, 0x55);
    try
    {
        //Try to fill 6th byte
        FillMemory(ptrAcross+5, 1, 0x55);
    }
    catch(...) // only catches the access violation when compiled with /EHa
    {
        std::cout << "Access violation" << std::endl;
    }
    std::cout << "Program finished" <<std::endl;
    VirtualFree(mem, 0, MEM_RELEASE);

    return 0;
}
PeterT
  • 7,981
  • 1
  • 26
  • 34
  • Thank you @PeterT, your solution is indeed enough for my problem. Actually, I need the last 2 bytes contain some specified bytes (not random). Could I do that by decommit (i.e. VirtualFree then VirtualAlloc again) the second page? – Ta Thanh Dinh Aug 04 '19 at 07:36
1

Basically, this answer does not add any thing more essential than one of @PeterT. It's modified a little bit to write arbitrary bytes (as we want) on the page boundary:

use std::{
    mem::{transmute, MaybeUninit},
    ptr,
};

use winapi::{
    shared::{minwindef::TRUE, ntdef::NULL},
    um::{
        memoryapi::{VirtualAlloc, VirtualFree, VirtualProtect},
        sysinfoapi::GetSystemInfo,
        winnt::{
            MEM_COMMIT, MEM_DECOMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
        },
    },
};

fn main() {
    let page_size = {
        let mut sys_info = MaybeUninit::uninit();
        let sys_info = unsafe {
            GetSystemInfo(sys_info.as_mut_ptr());
            sys_info.assume_init()
        };
        sys_info.dwPageSize
    } as usize;

    let region_base = unsafe {
        VirtualAlloc(
            NULL,
            page_size * 2,
            MEM_RESERVE | MEM_COMMIT,
            PAGE_EXECUTE_READWRITE,
        )
    } as usize;

    println!("Allocated region base: 0x{:x}", region_base);

    let ud1_addr = region_base + page_size - 0x2;
    print!("Writing 0f b9 27 (ud1 esp, [rdi]) to: 0x{:x}... ", ud1_addr);

    let ud1_ptr = ud1_addr as *mut u8;
    unsafe {
        ptr::write(ud1_ptr, 0x0f);
        ptr::write(ud1_ptr.add(1), 0xb9);
        ptr::write(ud1_ptr.add(2), 0x27);
    };

    println!("ok");

    let last_page_addr: usize = region_base + page_size;
    print!("Decommitting the last page at 0x{:x}... ", last_page_addr);
    let free_ok = unsafe { VirtualFree(last_page_addr as _, page_size, MEM_DECOMMIT) };

    if free_ok == TRUE {
        println!("ok. Executing: ud1 esp, [rdi]");
        let ud1: extern "C" fn() = unsafe { transmute(ud1_ptr as *const ()) };
        ud1();
    } else {
        println!("failed");
    }
}
Ta Thanh Dinh
  • 638
  • 1
  • 5
  • 12