Ta Thanh Dinh
Ta Thanh Dinh

Reputation: 638

Triggering page fault exception on Windows

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?

Upvotes: 0

Views: 589

Answers (2)

Ta Thanh Dinh
Ta Thanh Dinh

Reputation: 638

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

Upvotes: 1

PeterT
PeterT

Reputation: 8284

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

Upvotes: 1

Related Questions